/* * 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/>. */ /* * MahalanobisEstimator.java * Copyright (C) 1999-2012 University of Waikato, Hamilton, New Zealand * */ package weka.estimators; import weka.core.Capabilities; import weka.core.Capabilities.Capability; import weka.core.RevisionUtils; import weka.core.Utils; import weka.core.matrix.Matrix; /** * Simple probability estimator that places a single normal distribution * over the observed values. * * @author Len Trigg (trigg@cs.waikato.ac.nz) * @version $Revision: 8034 $ */ public class MahalanobisEstimator extends Estimator implements IncrementalEstimator { /** for serialization */ private static final long serialVersionUID = 8950225468990043868L; /** The inverse of the covariance matrix */ private Matrix m_CovarianceInverse; /** The determinant of the covariance matrix */ private double m_Determinant; /** * The difference between the conditioning value and the conditioning mean */ private double m_ConstDelta; /** The mean of the values */ private double m_ValueMean; /** 2 * PI */ private static double TWO_PI = 2 * Math.PI; /** * Returns value for normal kernel * * @param x the argument to the kernel function * @param variance the variance * @return the value for a normal kernel */ private double normalKernel(double x) { Matrix thisPoint = new Matrix(1, 2); thisPoint.set(0, 0, x); thisPoint.set(0, 1, m_ConstDelta); return Math.exp(-thisPoint.times(m_CovarianceInverse). times(thisPoint.transpose()).get(0, 0) / 2) / (Math.sqrt(TWO_PI) * m_Determinant); } /** * Constructor * * @param covariance * @param constDelta * @param valueMean */ public MahalanobisEstimator(Matrix covariance, double constDelta, double valueMean) { m_CovarianceInverse = null; if ((covariance.getRowDimension() == 2) && (covariance.getColumnDimension() == 2)) { double a = covariance.get(0, 0); double b = covariance.get(0, 1); double c = covariance.get(1, 0); double d = covariance.get(1, 1); if (a == 0) { a = c; c = 0; double temp = b; b = d; d = temp; } if (a == 0) { return; } double denom = d - c * b / a; if (denom == 0) { return; } m_Determinant = covariance.get(0, 0) * covariance.get(1, 1) - covariance.get(1, 0) * covariance.get(0, 1); m_CovarianceInverse = new Matrix(2, 2); m_CovarianceInverse.set(0, 0, 1.0 / a + b * c / a / a / denom); m_CovarianceInverse.set(0, 1, -b / a / denom); m_CovarianceInverse.set(1, 0, -c / a / denom); m_CovarianceInverse.set(1, 1, 1.0 / denom); m_ConstDelta = constDelta; m_ValueMean = valueMean; } } /** * Add a new data value to the current estimator. Does nothing because the * data is provided in the constructor. * * @param data the new data value * @param weight the weight assigned to the data value */ public void addValue(double data, double weight) { } /** * Get a probability estimate for a value * * @param data the value to estimate the probability of * @return the estimated probability of the supplied value */ public double getProbability(double data) { double delta = data - m_ValueMean; if (m_CovarianceInverse == null) { return 0; } return normalKernel(delta); } /** Display a representation of this estimator */ public String toString() { if (m_CovarianceInverse == null) { return "No covariance inverse\n"; } return "Mahalanovis Distribution. Mean = " + Utils.doubleToString(m_ValueMean, 4, 2) + " ConditionalOffset = " + Utils.doubleToString(m_ConstDelta, 4, 2) + "\n" + "Covariance Matrix: Determinant = " + m_Determinant + " Inverse:\n" + m_CovarianceInverse; } /** * Returns default capabilities of the classifier. * * @return the capabilities of this classifier */ public Capabilities getCapabilities() { Capabilities result = super.getCapabilities(); result.disableAll(); // class if (!m_noClass) { result.enable(Capability.NOMINAL_CLASS); result.enable(Capability.MISSING_CLASS_VALUES); } else { result.enable(Capability.NO_CLASS); } // attributes result.enable(Capability.NUMERIC_ATTRIBUTES); return result; } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } /** * Main method for testing this class. * * @param argv should contain a sequence of numeric values */ public static void main(String [] argv) { try { double delta = 0.5; double xmean = 0; double lower = 0; double upper = 10; Matrix covariance = new Matrix(2, 2); covariance.set(0, 0, 2); covariance.set(0, 1, -3); covariance.set(1, 0, -4); covariance.set(1, 1, 5); if (argv.length > 0) { covariance.set(0, 0, Double.valueOf(argv[0]).doubleValue()); } if (argv.length > 1) { covariance.set(0, 1, Double.valueOf(argv[1]).doubleValue()); } if (argv.length > 2) { covariance.set(1, 0, Double.valueOf(argv[2]).doubleValue()); } if (argv.length > 3) { covariance.set(1, 1, Double.valueOf(argv[3]).doubleValue()); } if (argv.length > 4) { delta = Double.valueOf(argv[4]).doubleValue(); } if (argv.length > 5) { xmean = Double.valueOf(argv[5]).doubleValue(); } MahalanobisEstimator newEst = new MahalanobisEstimator(covariance, delta, xmean); if (argv.length > 6) { lower = Double.valueOf(argv[6]).doubleValue(); if (argv.length > 7) { upper = Double.valueOf(argv[7]).doubleValue(); } double increment = (upper - lower) / 50; for(double current = lower; current <= upper; current+= increment) System.out.println(current + " " + newEst.getProbability(current)); } else { System.out.println("Covariance Matrix\n" + covariance); System.out.println(newEst); } } catch (Exception e) { System.out.println(e.getMessage()); } } }