[Home] Restricted access for guests. The link to Java source code is disabled
Java source code of 'jhplot.math.pca.PCA'
package jhplot.math.pca;
import jhplot.math.pca.covmatrixevd.*;
import java.lang.Math;
import Jama.Matrix;
/** The class responsible mainly for preparing the PCA transformation parameters
* based on training data and executing the actual transformation on test data.
* @author Mateusz Kobos
*/
public final class PCA {
/** Type of the possible data transformation.
* ROTATION: rotate the data matrix to get a diagonal covariance matrix.
* This transformation is sometimes simply called PCA.
* WHITENING: rotate and scale the data matrix to get
* the unit covariance matrix
*/
public enum TransformationType { ROTATION, WHITENING };
/** Whether the input data matrix should be centered. */
private final boolean centerMatrix;
/** Number of input dimensions. */
private final int inputDim;
private final Matrix whiteningTransformation;
private final Matrix pcaRotationTransformation;
private final Matrix v;
/** Part of the original SVD vector that is responsible for transforming the
* input data into a vector of zeros.*/
private final Matrix zerosRotationTransformation;
private final Matrix d;
private final double[] means;
private final double threshold;
/** Create the PCA transformation. Use the popular SVD method for internal
* calculations
* @param data data matrix used to compute the PCA transformation.
* Rows of the matrix are the instances/samples, columns are dimensions.
* It is assumed that the matrix is already centered.
* */
public PCA(Matrix data){
this(data, new SVDBased(), true);
}
/** Create the PCA transformation. Use the popular SVD method for internal
* calculations
* @param data data matrix used to compute the PCA transformation.
* Rows of the matrix are the instances/samples, columns are dimensions.
* @param center should the data matrix be centered before doing the
* calculations?
* */
public PCA(Matrix data, boolean center){
this(data, new SVDBased(), center);
}
/** Create the PCA transformation.
* @param data data matrix used to compute the PCA transformation.
* Rows of the matrix are the instances/samples, columns are dimensions.
* It is assumed that the matrix is already centered.
* @param evdCalc method of computing eigenvalue decomposition of data's
* covariance matrix
* */
public PCA(Matrix data, CovarianceMatrixEVDCalculator evdCalc){
this(data, evdCalc, true);
}
/** Create the PCA transformation
* @param data data matrix used to compute the PCA transformation.
* Rows of the matrix are the instances/samples, columns are dimensions.
* @param evdCalc method of computing eigenvalue decomposition of data's
* covariance matrix
* @param center should the data matrix be centered before doing the
* calculations?
*/
public PCA(Matrix data, CovarianceMatrixEVDCalculator evdCalc, boolean center){
/** Determine if input matrix should be centered */
this.centerMatrix = center;
/** Get the number of input dimensions. */
this.inputDim = data.getColumnDimension();
this.means = getColumnsMeans(data);
Matrix centeredData = data;
/** Center the data matrix columns about zero */
if(centerMatrix){
centeredData = shiftColumns(data, means);
}
//debugWrite(centeredData, "centeredData.csv");
EVDResult evd = evdCalc.run(centeredData);
EVDWithThreshold evdT = new EVDWithThreshold(evd);
/** Get only the values of the matrices that correspond to
* standard deviations above the threshold*/
this.d = evdT.getDAboveThreshold();
this.v = evdT.getVAboveThreshold();
this.zerosRotationTransformation = evdT.getVBelowThreshold();
/** A 3-sigma-like ad-hoc rule */
this.threshold = 3*evdT.getThreshold();
//debugWrite(this.evd.v, "eigen-v.csv");
//debugWrite(this.evd.d, "eigen-d.csv");
Matrix sqrtD = sqrtDiagonalMatrix(d);
Matrix scaling = inverseDiagonalMatrix(sqrtD);
//debugWrite(scaling, "scaling.csv");
this.pcaRotationTransformation = v;
this.whiteningTransformation =
this.pcaRotationTransformation.times(scaling);
}
/**
* @return matrix where eigenvectors are placed in columns
*/
public Matrix getEigenvectorsMatrix(){
return v;
}
/**
* Get selected eigenvalue
* @param dimNo dimension number corresponding to given eigenvalue
*/
public double getEigenvalue(int dimNo){
return d.get(dimNo, dimNo);
}
/**
* Get number of dimensions of the input vectors
*/
public int getInputDimsNo(){
return inputDim;
}
/**
* Get number of dimensions of the output vectors
*/
public int getOutputDimsNo(){
return v.getColumnDimension();
}
/**
* Execute selected transformation on given data.
* @param data data to transform. Rows of the matrix are the
* instances/samples, columns are dimensions.
* If the original PCA data matrix was set to be centered, this
* matrix will also be centered using the same parameters.
* @param type transformation to apply
* @return transformed data
*/
public Matrix transform(Matrix data, TransformationType type){
Matrix centeredData = data;
if(centerMatrix){
centeredData = shiftColumns(data, means);
}
Matrix transformation = getTransformation(type);
return centeredData.times(transformation);
}
/**
* Check if given point lies in PCA-generated subspace.
* If it does not, it means that the point doesn't belong
* to the transformation domain i.e. it is an outlier.
* @param pt point. If the original PCA data matrix was set to be centered,
* this point will also be centered using the same parameters.
* @return true iff the point lies on all principal axes
*/
public boolean belongsToGeneratedSubspace(Matrix pt){
Assume.assume(pt.getRowDimension()==1);
Matrix centeredPt = pt;
if(centerMatrix){
centeredPt = shiftColumns(pt, means);
}
Matrix zerosTransformedPt = centeredPt.times(zerosRotationTransformation);
assert zerosTransformedPt.getRowDimension()==1;
/** Check if all coordinates of the point were zeroed by the
* transformation */
for(int c = 0; c < zerosTransformedPt.getColumnDimension(); c++)
if(Math.abs(zerosTransformedPt.get(0, c)) > threshold) {
return false;
}
return true;
}
/**
* Function for JUnit testing purposes only
* */
public static Matrix calculateCovarianceMatrix(Matrix data){
double[] means = getColumnsMeans(data);
Matrix centeredData = shiftColumns(data, means);
return EVDBased.calculateCovarianceMatrixOfCenteredData(
centeredData);
}
private Matrix getTransformation(TransformationType type){
switch(type){
case ROTATION: return pcaRotationTransformation;
case WHITENING: return whiteningTransformation;
default: throw new RuntimeException("Unknown enum type: "+type);
}
}
private static Matrix shiftColumns(Matrix data, double[] shifts){
Assume.assume(shifts.length==data.getColumnDimension());
Matrix m = new Matrix(
data.getRowDimension(), data.getColumnDimension());
for(int c = 0; c < data.getColumnDimension(); c++)
for(int r = 0; r < data.getRowDimension(); r++)
m.set(r, c, data.get(r, c) - shifts[c]);
return m;
}
private static double[] getColumnsMeans(Matrix m){
double[] means = new double[m.getColumnDimension()];
for(int c = 0; c < m.getColumnDimension(); c++){
double sum = 0;
for(int r = 0; r < m.getRowDimension(); r++)
sum += m.get(r, c);
means[c] = sum/m.getRowDimension();
}
return means;
}
private static Matrix sqrtDiagonalMatrix(Matrix m){
assert m.getRowDimension()==m.getColumnDimension();
Matrix newM = new Matrix(m.getRowDimension(), m.getRowDimension());
for(int i = 0; i < m.getRowDimension(); i++)
newM.set(i, i, Math.sqrt(m.get(i, i)));
return newM;
}
private static Matrix inverseDiagonalMatrix(Matrix m){
assert m.getRowDimension()==m.getColumnDimension();
Matrix newM = new Matrix(m.getRowDimension(), m.getRowDimension());
for(int i = 0; i < m.getRowDimension(); i++)
newM.set(i, i, 1/m.get(i, i));
return newM;
}
}
/**
* Version of the eigenvalue decomposition where values of standard deviations
* (i.e. square roots of the eigenvalues) below a certain threshold are omitted.
*/
class EVDWithThreshold {
/** Double machine precision in the R environment
* (i.e. in the R environment: {@code .Machine$double.eps}) */
public static final double precision = 2.220446e-16;
private final EVDResult evd;
private final double threshold;
/**
* The tol parameter of the method assumes a default value equal to
* {@code sqrt(.Machine$double.eps)} from the R environment. In the help
* page of the R environment {@code prcomp} function
* (see the paragraph on {@code tol} parameter) it is written that
* in such setting we will "omit essentially constant components". */
public EVDWithThreshold(EVDResult evd){
this(evd, Math.sqrt(precision));
}
/**
* @param tol threshold parameter of the method - the same parameter
* as used in R environment's `prcomp` function (see the paragraph on
* {@code tol} parameter). */
public EVDWithThreshold(EVDResult evd, double tol){
this.evd = evd;
this.threshold = firstComponentSD(evd)*tol;
}
private static double firstComponentSD(EVDResult evd){
return Math.sqrt(evd.d.get(0, 0));
}
/** Magnitude below which components should be omitted. This parameter
* corresponds to the one in the `prcomp` function from the R
* environment (see the paragraph on {@code tol} parameter).
* The components are omitted if their standard deviations are less than
* or equal to {@code tol} (a parameter given in the constructor) times
* the standard deviation of the first component.
*/
public double getThreshold(){
return threshold;
}
public Matrix getDAboveThreshold(){
int aboveThresholdElemsNo = getElementsNoAboveThreshold();
Matrix newD = evd.d.getMatrix(0, aboveThresholdElemsNo-1,
0, aboveThresholdElemsNo-1);
return newD;
}
public Matrix getVAboveThreshold(){
return evd.v.getMatrix(0, evd.v.getRowDimension()-1,
0, getElementsNoAboveThreshold()-1);
}
public Matrix getVBelowThreshold(){
return evd.v.getMatrix(0, evd.v.getRowDimension()-1,
getElementsNoAboveThreshold(), evd.v.getColumnDimension()-1);
}
private int getElementsNoAboveThreshold(){
for(int i = 0; i < evd.d.getColumnDimension(); i++){
double val = Math.sqrt(evd.d.get(i, i));
if(!(val > threshold)) return i;
}
return evd.d.getColumnDimension();
}
}