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    *      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  
18  package org.apache.commons.math4.legacy.stat.descriptive.moment;
19  
20  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
21  import org.apache.commons.math4.legacy.exception.NullArgumentException;
22  import org.apache.commons.math4.legacy.stat.descriptive.AbstractUnivariateStatistic;
23  import org.apache.commons.math4.legacy.core.MathArrays;
24  
25  /**
26   * <p>Computes the semivariance of a set of values with respect to a given cutoff value.
27   * We define the <i>downside semivariance</i> of a set of values <code>x</code>
28   * against the <i>cutoff value</i> <code>cutoff</code> to be <br>
29   * <code>&Sigma; (x[i] - target)<sup>2</sup> / df</code> <br>
30   * where the sum is taken over all <code>i</code> such that {@code x[i] < cutoff}
31   * and <code>df</code> is the length of <code>x</code> (non-bias-corrected) or
32   * one less than this number (bias corrected).  The <i>upside semivariance</i>
33   * is defined similarly, with the sum taken over values of <code>x</code> that
34   * exceed the cutoff value.</p>
35   *
36   * <p>The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
37   * and the "variance direction" (upside or downside) defaults to downside.  The variance direction
38   * and bias correction may be set using property setters or their values can provided as
39   * parameters to {@link #evaluate(double[], double, Direction, boolean, int, int)}.</p>
40   *
41   * <p>If the input array is null, <code>evaluate</code> methods throw
42   * <code>IllegalArgumentException.</code>  If the array has length 1, <code>0</code>
43   * is returned, regardless of the value of the <code>cutoff.</code>
44   *
45   * <p><strong>Note that this class is not intended to be threadsafe.</strong> If
46   * multiple threads access an instance of this class concurrently, and one or
47   * more of these threads invoke property setters, external synchronization must
48   * be provided to ensure correct results.</p>
49   *
50   * @since 2.1
51   */
52  public class SemiVariance extends AbstractUnivariateStatistic {
53  
54      /**
55       * The UPSIDE Direction is used to specify that the observations above the
56       * cutoff point will be used to calculate SemiVariance.
57       */
58      public static final Direction UPSIDE_VARIANCE = Direction.UPSIDE;
59  
60      /**
61       * The DOWNSIDE Direction is used to specify that the observations below.
62       * the cutoff point will be used to calculate SemiVariance
63       */
64      public static final Direction DOWNSIDE_VARIANCE = Direction.DOWNSIDE;
65  
66      /**
67       * Determines whether or not bias correction is applied when computing the
68       * value of the statistic.  True means that bias is corrected.
69       */
70      private boolean biasCorrected = true;
71  
72      /**
73       * Determines whether to calculate downside or upside SemiVariance.
74       */
75      private Direction varianceDirection = Direction.DOWNSIDE;
76  
77      /**
78       * Constructs a SemiVariance with default (true) <code>biasCorrected</code>
79       * property and default (Downside) <code>varianceDirection</code> property.
80       */
81      public SemiVariance() {
82      }
83  
84      /**
85       * Constructs a SemiVariance with the specified <code>biasCorrected</code>
86       * property and default (Downside) <code>varianceDirection</code> property.
87       *
88       * @param biasCorrected  setting for bias correction - true means
89       * bias will be corrected and is equivalent to using the "no arg"
90       * constructor
91       */
92      public SemiVariance(final boolean biasCorrected) {
93          this.biasCorrected = biasCorrected;
94      }
95  
96      /**
97       * Constructs a SemiVariance with the specified <code>Direction</code> property.
98       * and default (true) <code>biasCorrected</code> property
99       *
100      * @param direction  setting for the direction of the SemiVariance
101      * to calculate
102      */
103     public SemiVariance(final Direction direction) {
104         this.varianceDirection = direction;
105     }
106 
107     /**
108      * Constructs a SemiVariance with the specified <code>isBiasCorrected</code>
109      * property and the specified <code>Direction</code> property.
110      *
111      * @param corrected  setting for bias correction - true means
112      * bias will be corrected and is equivalent to using the "no arg"
113      * constructor
114      *
115      * @param direction  setting for the direction of the SemiVariance
116      * to calculate
117      */
118     public SemiVariance(final boolean corrected, final Direction direction) {
119         this.biasCorrected = corrected;
120         this.varianceDirection = direction;
121     }
122 
123     /**
124      * Copy constructor, creates a new {@code SemiVariance} identical
125      * to the {@code original}.
126      *
127      * @param original the {@code SemiVariance} instance to copy
128      * @throws NullArgumentException  if original is null
129      */
130     public SemiVariance(final SemiVariance original) throws NullArgumentException {
131         copy(original, this);
132     }
133 
134     /**
135      * {@inheritDoc}
136      */
137     @Override
138     public SemiVariance copy() {
139         SemiVariance result = new SemiVariance();
140         // No try-catch or advertised exception because args are guaranteed non-null
141         copy(this, result);
142         return result;
143     }
144 
145     /**
146      * Copies source to dest.
147      * <p>Neither source nor dest can be null.</p>
148      *
149      * @param source SemiVariance to copy
150      * @param dest SemiVariance to copy to
151      * @throws NullArgumentException if either source or dest is null
152      */
153     public static void copy(final SemiVariance source, SemiVariance dest)
154         throws NullArgumentException {
155         NullArgumentException.check(source);
156         NullArgumentException.check(dest);
157         dest.biasCorrected = source.biasCorrected;
158         dest.varianceDirection = source.varianceDirection;
159     }
160 
161     /**
162      * <p>Returns the {@link SemiVariance} of the designated values against the mean, using
163      * instance properties varianceDirection and biasCorrection.</p>
164      *
165      * <p>Returns <code>NaN</code> if the array is empty and throws
166      * <code>IllegalArgumentException</code> if the array is null.</p>
167      *
168      * @param values the input array
169      * @param start index of the first array element to include
170      * @param length the number of elements to include
171      * @return the SemiVariance
172      * @throws MathIllegalArgumentException if the parameters are not valid
173      *
174      */
175      @Override
176      public double evaluate(final double[] values, final int start, final int length)
177          throws MathIllegalArgumentException {
178          double m = (new Mean()).evaluate(values, start, length);
179          return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length);
180      }
181 
182      /**
183       * This method calculates {@link SemiVariance} for the entire array against the mean, using
184       * the current value of the biasCorrection instance property.
185       *
186       * @param values the input array
187       * @param direction the {@link Direction} of the semivariance
188       * @return the SemiVariance
189       * @throws MathIllegalArgumentException if values is null
190       *
191       */
192      public double evaluate(final double[] values, Direction direction)
193          throws MathIllegalArgumentException {
194          double m = (new Mean()).evaluate(values);
195          return evaluate(values, m, direction, biasCorrected, 0, values.length);
196      }
197 
198      /**
199       * <p>Returns the {@link SemiVariance} of the designated values against the cutoff, using
200       * instance properties variancDirection and biasCorrection.</p>
201       *
202       * <p>Returns <code>NaN</code> if the array is empty and throws
203       * <code>MathIllegalArgumentException</code> if the array is null.</p>
204       *
205       * @param values the input array
206       * @param cutoff the reference point
207       * @return the SemiVariance
208       * @throws MathIllegalArgumentException if values is null
209       */
210      public double evaluate(final double[] values, final double cutoff)
211          throws MathIllegalArgumentException {
212          return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
213      }
214 
215      /**
216       * <p>Returns the {@link SemiVariance} of the designated values against the cutoff in the
217       * given direction, using the current value of the biasCorrection instance property.</p>
218       *
219       * <p>Returns <code>NaN</code> if the array is empty and throws
220       * <code>MathIllegalArgumentException</code> if the array is null.</p>
221       *
222       * @param values the input array
223       * @param cutoff the reference point
224       * @param direction the {@link Direction} of the semivariance
225       * @return the SemiVariance
226       * @throws MathIllegalArgumentException if values is null
227       */
228      public double evaluate(final double[] values, final double cutoff, final Direction direction)
229          throws MathIllegalArgumentException {
230          return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
231      }
232 
233      /**
234       * <p>Returns the {@link SemiVariance} of the designated values against the cutoff
235       * in the given direction with the provided bias correction.</p>
236       *
237       * <p>Returns <code>NaN</code> if the array is empty and throws
238       * <code>IllegalArgumentException</code> if the array is null.</p>
239       *
240       * @param values the input array
241       * @param cutoff the reference point
242       * @param direction the {@link Direction} of the semivariance
243       * @param corrected the BiasCorrection flag
244       * @param start index of the first array element to include
245       * @param length the number of elements to include
246       * @return the SemiVariance
247       * @throws MathIllegalArgumentException if the parameters are not valid
248       *
249       */
250      public double evaluate (final double[] values, final double cutoff, final Direction direction,
251                              final boolean corrected, final int start, final int length)
252          throws MathIllegalArgumentException {
253 
254          MathArrays.verifyValues(values, start, length);
255          if (values.length == 0) {
256              return Double.NaN;
257          } else {
258              if (values.length == 1) {
259                  return 0.0;
260              } else {
261                  final boolean booleanDirection = direction.getDirection();
262 
263                  double dev = 0.0;
264                  double sumsq = 0.0;
265                  for (int i = start; i < length; i++) {
266                      if ((values[i] > cutoff) == booleanDirection) {
267                          dev = values[i] - cutoff;
268                          sumsq += dev * dev;
269                      }
270                  }
271 
272                  if (corrected) {
273                      return sumsq / (length - 1.0);
274                  } else {
275                      return sumsq / length;
276                  }
277              }
278          }
279      }
280 
281      /**
282       * Returns true iff biasCorrected property is set to true.
283       *
284       * @return the value of biasCorrected.
285       */
286      public boolean isBiasCorrected() {
287          return biasCorrected;
288      }
289 
290      /**
291       * Sets the biasCorrected property.
292       *
293       * @param biasCorrected new biasCorrected property value
294       */
295      public void setBiasCorrected(boolean biasCorrected) {
296          this.biasCorrected = biasCorrected;
297      }
298 
299      /**
300       * Returns the varianceDirection property.
301       *
302       * @return the varianceDirection
303       */
304      public Direction getVarianceDirection () {
305          return varianceDirection;
306      }
307 
308      /**
309       * Sets the variance direction.
310       *
311       * @param varianceDirection the direction of the semivariance
312       */
313      public void setVarianceDirection(Direction varianceDirection) {
314          this.varianceDirection = varianceDirection;
315      }
316 
317      /**
318       * The direction of the semivariance - either upside or downside. The direction
319       * is represented by boolean, with true corresponding to UPSIDE semivariance.
320       */
321      public enum Direction {
322          /**
323           * The UPSIDE Direction is used to specify that the observations above the.
324           * cutoff point will be used to calculate SemiVariance
325           */
326          UPSIDE (true),
327 
328          /**
329           * The DOWNSIDE Direction is used to specify that the observations below.
330           * the cutoff point will be used to calculate SemiVariance
331           */
332          DOWNSIDE (false);
333 
334          /**
335           * boolean value  UPSIDE <-> true.
336           */
337          private boolean direction;
338 
339          /**
340           * Create a Direction with the given value.
341           *
342           * @param b boolean value representing the Direction. True corresponds to UPSIDE.
343           */
344          Direction (boolean b) {
345              direction = b;
346          }
347 
348          /**
349           * Returns the value of this Direction. True corresponds to UPSIDE.
350           *
351           * @return true if direction is UPSIDE; false otherwise
352           */
353          boolean getDirection () {
354              return direction;
355          }
356      }
357 }