View Javadoc
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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  
23  package org.hipparchus.stat.correlation;
24  
25  import org.hipparchus.exception.LocalizedCoreFormats;
26  import org.hipparchus.exception.MathIllegalArgumentException;
27  import org.hipparchus.linear.BlockRealMatrix;
28  import org.hipparchus.linear.RealMatrix;
29  import org.hipparchus.stat.LocalizedStatFormats;
30  import org.hipparchus.stat.ranking.NaNStrategy;
31  import org.hipparchus.stat.ranking.NaturalRanking;
32  import org.hipparchus.stat.ranking.RankingAlgorithm;
33  import org.hipparchus.util.MathArrays;
34  
35  /**
36   * Spearman's rank correlation. This implementation performs a rank
37   * transformation on the input data and then computes {@link PearsonsCorrelation}
38   * on the ranked data.
39   * <p>
40   * By default, ranks are computed using {@link NaturalRanking} with default
41   * strategies for handling NaNs and ties in the data (NaNs maximal, ties averaged).
42   * The ranking algorithm can be set using a constructor argument.
43   */
44  public class SpearmansCorrelation {
45  
46      /** Input data */
47      private final RealMatrix data;
48  
49      /** Ranking algorithm  */
50      private final RankingAlgorithm rankingAlgorithm;
51  
52      /** Rank correlation */
53      private final PearsonsCorrelation rankCorrelation;
54  
55      /**
56       * Create a SpearmansCorrelation without data.
57       */
58      public SpearmansCorrelation() {
59          this(new NaturalRanking());
60      }
61  
62      /**
63       * Create a SpearmansCorrelation with the given ranking algorithm.
64       *
65       * @param rankingAlgorithm ranking algorithm
66       * @throws MathIllegalArgumentException if the provided {@link RankingAlgorithm} is of
67       * type {@link NaturalRanking} and uses a {@link NaNStrategy#REMOVED} strategy
68       */
69      public SpearmansCorrelation(final RankingAlgorithm rankingAlgorithm)
70          throws MathIllegalArgumentException {
71  
72          if (rankingAlgorithm instanceof NaturalRanking &&
73              NaNStrategy.REMOVED == ((NaturalRanking) rankingAlgorithm).getNanStrategy()) {
74              throw new MathIllegalArgumentException(LocalizedStatFormats.NOT_SUPPORTED_NAN_STRATEGY,
75                                                     NaNStrategy.REMOVED);
76          }
77  
78          data = null;
79          this.rankingAlgorithm = rankingAlgorithm;
80          rankCorrelation = null;
81      }
82  
83      /**
84       * Create a SpearmansCorrelation from the given data matrix.
85       *
86       * @param dataMatrix matrix of data with columns representing
87       * variables to correlate
88       */
89      public SpearmansCorrelation(final RealMatrix dataMatrix) {
90          this(dataMatrix, new NaturalRanking());
91      }
92  
93      /**
94       * Create a SpearmansCorrelation with the given input data matrix
95       * and ranking algorithm.
96       *
97       * @param dataMatrix matrix of data with columns representing
98       * variables to correlate
99       * @param rankingAlgorithm ranking algorithm
100      * @throws MathIllegalArgumentException if the provided {@link RankingAlgorithm} is of
101      * type {@link NaturalRanking} and uses a {@link NaNStrategy#REMOVED} strategy
102      */
103     public SpearmansCorrelation(final RealMatrix dataMatrix, final RankingAlgorithm rankingAlgorithm)
104         throws MathIllegalArgumentException {
105 
106         if (rankingAlgorithm instanceof NaturalRanking &&
107             NaNStrategy.REMOVED == ((NaturalRanking) rankingAlgorithm).getNanStrategy()) {
108             throw new MathIllegalArgumentException(LocalizedStatFormats.NOT_SUPPORTED_NAN_STRATEGY,
109                                                    NaNStrategy.REMOVED);
110         }
111 
112         this.rankingAlgorithm = rankingAlgorithm;
113         this.data = rankTransform(dataMatrix);
114         rankCorrelation = new PearsonsCorrelation(data);
115     }
116 
117     /**
118      * Calculate the Spearman Rank Correlation Matrix.
119      *
120      * @return Spearman Rank Correlation Matrix
121      * @throws NullPointerException if this instance was created with no data
122      */
123     public RealMatrix getCorrelationMatrix() {
124         return rankCorrelation.getCorrelationMatrix();
125     }
126 
127     /**
128      * Returns a {@link PearsonsCorrelation} instance constructed from the
129      * ranked input data. That is,
130      * <code>new SpearmansCorrelation(matrix).getRankCorrelation()</code>
131      * is equivalent to
132      * <code>new PearsonsCorrelation(rankTransform(matrix))</code> where
133      * <code>rankTransform(matrix)</code> is the result of applying the
134      * configured <code>RankingAlgorithm</code> to each of the columns of
135      * <code>matrix.</code>
136      *
137      * <p>Returns null if this instance was created with no data.</p>
138      *
139      * @return PearsonsCorrelation among ranked column data
140      */
141     public PearsonsCorrelation getRankCorrelation() {
142         return rankCorrelation;
143     }
144 
145     /**
146      * Computes the Spearman's rank correlation matrix for the columns of the
147      * input matrix.
148      *
149      * @param matrix matrix with columns representing variables to correlate
150      * @return correlation matrix
151      */
152     public RealMatrix computeCorrelationMatrix(final RealMatrix matrix) {
153         final RealMatrix matrixCopy = rankTransform(matrix);
154         return new PearsonsCorrelation().computeCorrelationMatrix(matrixCopy);
155     }
156 
157     /**
158      * Computes the Spearman's rank correlation matrix for the columns of the
159      * input rectangular array.  The columns of the array represent values
160      * of variables to be correlated.
161      *
162      * @param matrix matrix with columns representing variables to correlate
163      * @return correlation matrix
164      */
165     public RealMatrix computeCorrelationMatrix(final double[][] matrix) {
166        return computeCorrelationMatrix(new BlockRealMatrix(matrix));
167     }
168 
169     /**
170      * Computes the Spearman's rank correlation coefficient between the two arrays.
171      *
172      * @param xArray first data array
173      * @param yArray second data array
174      * @return Returns Spearman's rank correlation coefficient for the two arrays
175      * @throws MathIllegalArgumentException if the arrays lengths do not match
176      * @throws MathIllegalArgumentException if the array length is less than 2
177      */
178     public double correlation(final double[] xArray, final double[] yArray) {
179         MathArrays.checkEqualLength(xArray, yArray);
180         if (xArray.length < 2) {
181             throw new MathIllegalArgumentException(LocalizedCoreFormats.INSUFFICIENT_DIMENSION,
182                                                    xArray.length, 2);
183         }
184 
185         return new PearsonsCorrelation().correlation(rankingAlgorithm.rank(xArray),
186                                                      rankingAlgorithm.rank(yArray));
187     }
188 
189     /**
190      * Applies rank transform to each of the columns of <code>matrix</code>
191      * using the current <code>rankingAlgorithm</code>.
192      *
193      * @param matrix matrix to transform
194      * @return a rank-transformed matrix
195      */
196     private RealMatrix rankTransform(final RealMatrix matrix) {
197         RealMatrix transformed = matrix.copy();
198         for (int i = 0; i < transformed.getColumnDimension(); i++) {
199             transformed.setColumn(i, rankingAlgorithm.rank(transformed.getColumn(i)));
200         }
201 
202         return transformed;
203     }
204 
205 }