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