/*
 *   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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 *    LinearRegression.java
 *    Copyright (C) 1999-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.classifiers.functions;

import java.util.Enumeration;
import java.util.Vector;

import weka.classifiers.AbstractClassifier;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.matrix.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.filters.Filter;
import weka.filters.supervised.attribute.NominalToBinary;
import weka.filters.unsupervised.attribute.ReplaceMissingValues;

/**
 <!-- 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>
 * 
 * <pre> -minimal
 *  Conserve memory, don't keep dataset header and means/stdevs.
 *  Model cannot be printed out if this option is enabled. (default: keep data)</pre>
 * 
 <!-- options-end -->
 *
 * @author Eibe Frank (eibe@cs.waikato.ac.nz)
 * @author Len Trigg (trigg@cs.waikato.ac.nz)
 * @version $Revision: 9768 $
 */
public class LinearRegression extends AbstractClassifier implements OptionHandler,
  WeightedInstancesHandler {
  
  /** for serialization */
  static final long serialVersionUID = -3364580862046573747L;

  /** Array for storing coefficients of linear regression. */
  protected double[] m_Coefficients;

  /** Which attributes are relevant? */
  protected boolean[] m_SelectedAttributes;

  /** Variable for storing transformed training data. */
  protected Instances m_TransformedData;

  /** The filter for removing missing values. */
  protected ReplaceMissingValues m_MissingFilter;

  /** The filter storing the transformation from nominal to 
      binary attributes. */
  protected NominalToBinary m_TransformFilter;

  /** The standard deviations of the class attribute */
  protected double m_ClassStdDev;

  /** The mean of the class attribute */
  protected double m_ClassMean;

  /** The index of the class attribute */
  protected int m_ClassIndex;

  /** The attributes means */
  protected double[] m_Means;

  /** The attribute standard deviations */
  protected double[] m_StdDevs;

  /** The current attribute selection method */
  protected 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? */
  protected boolean m_EliminateColinearAttributes = true;

  /** Turn off all checks and conversions? */
  protected boolean m_checksTurnedOff = false;

  /** The ridge parameter */
  protected double m_Ridge = 1.0e-8;

  /** Conserve memory? */
  protected boolean m_Minimal = false;

  /** Model already built? */
  protected boolean m_ModelBuilt = 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 {
    m_ModelBuilt = false;
    
    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
    if (m_Minimal) {
      m_TransformedData = null;
      m_Means           = null;
      m_StdDevs         = null;
    }
    else {
      m_TransformedData = new Instances(data, 0);
    }
    
    m_ModelBuilt = true;
  }

  /**
   * 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_ModelBuilt)
      return "Linear Regression: No model built yet.";
    
    if (m_Minimal)
      return "Linear Regression: Model built.";
    
    try {
      StringBuilder text = new StringBuilder();
      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();

    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>"));

    newVector.addElement(
	new Option(
	    "\tConserve memory, don't keep dataset header and means/stdevs.\n"
	    + "\tModel cannot be printed out if this option is enabled."
	    + "\t(default: keep data)",
	    "minimal", 0, "-minimal"));
    
    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>
   * 
   * <pre> -minimal
   *  Conserve memory, don't keep dataset header and means/stdevs.
   *  Model cannot be printed out if this option is enabled. (default: keep data)</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));
    setMinimal(Utils.getFlag("minimal", 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() {
    Vector<String>	result;
    
    result = new Vector<String>();

    result.add("-S");
    result.add("" + getAttributeSelectionMethod().getSelectedTag().getID());
    
    if (getDebug())
      result.add("-D");
    
    if (!getEliminateColinearAttributes())
      result.add("-C");

    result.add("-R");
    result.add("" + getRidge());

    if (getMinimal())
      result.add("-minimal");
    
    return result.toArray(new String[result.size()]);
  }
  
  /**
   * 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 minimalTipText() {
    return "If enabled, dataset header, means and stdevs get discarded to conserve memory; also, the model cannot be printed out.";
  }

  /**
   * Sets whether to be more memory conservative or being able to output
   * the model as string.
   *
   * @param value	if true memory will be conserved
   */
  public void setMinimal(boolean value) {
    m_Minimal = value;
  }

  /**
   * Returns whether to be more memory conservative or being able to output
   * the model as string.
   *
   * @return 		true if memory conservation is preferred over 
   * 			outputting model description
   */
  public boolean getMinimal() {
    return m_Minimal;
  }

  /**
   * 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;
  }

  /**
   * 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
   */
  protected 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 (m_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
   */
  protected 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 (m_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 (m_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 (m_Debug) {
	      System.out.println("(akaike: " + currentAkaike);
	    }

	    // If it is better than the current best
	    if (currentAkaike < akaike) {
	      if (m_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 (m_Debug) {
	    System.out.println("(akaike: " + currentAkaike);
	  }

	  // If it is better than the current best
	  if (currentAkaike < akaike) {
	    if (m_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
   */
  protected 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
   */
  protected 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.
   */
  protected double[] doRegression(boolean[] selectedAttributes) 
  throws Exception {

    if (m_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;
    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);
        double sqrt_weight = Math.sqrt(inst.weight());
	int column = 0;
	for (int j = 0; j < m_TransformedData.numAttributes(); j++) {
	  if (j == m_ClassIndex) {
      dependent.set(i, 0, inst.classValue() * sqrt_weight);
	  } 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.set(i, column, value * sqrt_weight);
	      column++;
	    }
	  }
	}
      }
    }

    // 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, m_Ridge).getCoefficients();
      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: 9768 $");
  }
 
  /**
   * Generates a linear regression function predictor.
   *
   * @param argv the options
   */
  public static void main(String argv[]) {
    runClassifier(new LinearRegression(), argv);
  }
}
