1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package org.apache.commons.math.stat.correlation;
19
20import org.apache.commons.math.MathRuntimeException;
21import org.apache.commons.math.exception.util.LocalizedFormats;
22import org.apache.commons.math.linear.BlockRealMatrix;
23import org.apache.commons.math.linear.RealMatrix;
24import org.apache.commons.math.stat.ranking.NaturalRanking;
25import org.apache.commons.math.stat.ranking.RankingAlgorithm;
26
27/**
28 * <p>Spearman's rank correlation. This implementation performs a rank
29 * transformation on the input data and then computes {@link PearsonsCorrelation}
30 * on the ranked data.</p>
31 *
32 * <p>By default, ranks are computed using {@link NaturalRanking} with default
33 * strategies for handling NaNs and ties in the data (NaNs maximal, ties averaged).
34 * The ranking algorithm can be set using a constructor argument.</p>
35 *
36 * @since 2.0
37 * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
38 */
39
40public class SpearmansCorrelation {
41
42    /** Input data */
43    private final RealMatrix data;
44
45    /** Ranking algorithm  */
46    private final RankingAlgorithm rankingAlgorithm;
47
48    /** Rank correlation */
49    private final PearsonsCorrelation rankCorrelation;
50
51    /**
52     * Create a SpearmansCorrelation with the given input data matrix
53     * and ranking algorithm.
54     *
55     * @param dataMatrix matrix of data with columns representing
56     * variables to correlate
57     * @param rankingAlgorithm ranking algorithm
58     */
59    public SpearmansCorrelation(final RealMatrix dataMatrix, final RankingAlgorithm rankingAlgorithm) {
60        this.data = dataMatrix.copy();
61        this.rankingAlgorithm = rankingAlgorithm;
62        rankTransform(data);
63        rankCorrelation = new PearsonsCorrelation(data);
64    }
65
66    /**
67     * Create a SpearmansCorrelation from the given data matrix.
68     *
69     * @param dataMatrix matrix of data with columns representing
70     * variables to correlate
71     */
72    public SpearmansCorrelation(final RealMatrix dataMatrix) {
73        this(dataMatrix, new NaturalRanking());
74    }
75
76    /**
77     * Create a SpearmansCorrelation without data.
78     */
79    public SpearmansCorrelation() {
80        data = null;
81        this.rankingAlgorithm = new NaturalRanking();
82        rankCorrelation = null;
83    }
84
85    /**
86     * Calculate the Spearman Rank Correlation Matrix.
87     *
88     * @return Spearman Rank Correlation Matrix
89     */
90    public RealMatrix getCorrelationMatrix() {
91        return rankCorrelation.getCorrelationMatrix();
92    }
93
94    /**
95     * Returns a {@link PearsonsCorrelation} instance constructed from the
96     * ranked input data. That is,
97     * <code>new SpearmansCorrelation(matrix).getRankCorrelation()</code>
98     * is equivalent to
99     * <code>new PearsonsCorrelation(rankTransform(matrix))</code> where
100     * <code>rankTransform(matrix)</code> is the result of applying the
101     * configured <code>RankingAlgorithm</code> to each of the columns of
102     * <code>matrix.</code>
103     *
104     * @return PearsonsCorrelation among ranked column data
105     */
106    public PearsonsCorrelation getRankCorrelation() {
107        return rankCorrelation;
108    }
109
110    /**
111     * Computes the Spearman's rank correlation matrix for the columns of the
112     * input matrix.
113     *
114     * @param matrix matrix with columns representing variables to correlate
115     * @return correlation matrix
116     */
117    public RealMatrix computeCorrelationMatrix(RealMatrix matrix) {
118        RealMatrix matrixCopy = matrix.copy();
119        rankTransform(matrixCopy);
120        return new PearsonsCorrelation().computeCorrelationMatrix(matrixCopy);
121    }
122
123    /**
124     * Computes the Spearman's rank correlation matrix for the columns of the
125     * input rectangular array.  The columns of the array represent values
126     * of variables to be correlated.
127     *
128     * @param matrix matrix with columns representing variables to correlate
129     * @return correlation matrix
130     */
131    public RealMatrix computeCorrelationMatrix(double[][] matrix) {
132       return computeCorrelationMatrix(new BlockRealMatrix(matrix));
133    }
134
135    /**
136     * Computes the Spearman's rank correlation coefficient between the two arrays.
137     *
138     * </p>Throws IllegalArgumentException if the arrays do not have the same length
139     * or their common length is less than 2</p>
140     *
141     * @param xArray first data array
142     * @param yArray second data array
143     * @return Returns Spearman's rank correlation coefficient for the two arrays
144     * @throws  IllegalArgumentException if the arrays lengths do not match or
145     * there is insufficient data
146     */
147    public double correlation(final double[] xArray, final double[] yArray)
148    throws IllegalArgumentException {
149        if (xArray.length != yArray.length) {
150            throw MathRuntimeException.createIllegalArgumentException(
151                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, xArray.length, yArray.length);
152        } else if (xArray.length < 2) {
153            throw MathRuntimeException.createIllegalArgumentException(
154                  LocalizedFormats.INSUFFICIENT_DIMENSION, xArray.length, 2);
155        } else {
156            return new PearsonsCorrelation().correlation(rankingAlgorithm.rank(xArray),
157                    rankingAlgorithm.rank(yArray));
158        }
159    }
160
161    /**
162     * Applies rank transform to each of the columns of <code>matrix</code>
163     * using the current <code>rankingAlgorithm</code>
164     *
165     * @param matrix matrix to transform
166     */
167    private void rankTransform(RealMatrix matrix) {
168        for (int i = 0; i < matrix.getColumnDimension(); i++) {
169            matrix.setColumn(i, rankingAlgorithm.rank(matrix.getColumn(i)));
170        }
171    }
172}
173