001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.math4.stat.descriptive.moment;
019
020import java.io.Serializable;
021
022import org.apache.commons.math4.exception.MathIllegalArgumentException;
023import org.apache.commons.math4.exception.NullArgumentException;
024import org.apache.commons.math4.stat.descriptive.AbstractUnivariateStatistic;
025import org.apache.commons.math4.util.MathArrays;
026import org.apache.commons.math4.util.MathUtils;
027
028/**
029 * <p>Computes the semivariance of a set of values with respect to a given cutoff value.
030 * We define the <i>downside semivariance</i> of a set of values <code>x</code>
031 * against the <i>cutoff value</i> <code>cutoff</code> to be <br>
032 * <code>&Sigma; (x[i] - target)<sup>2</sup> / df</code> <br>
033 * where the sum is taken over all <code>i</code> such that {@code x[i] < cutoff}
034 * and <code>df</code> is the length of <code>x</code> (non-bias-corrected) or
035 * one less than this number (bias corrected).  The <i>upside semivariance</i>
036 * is defined similarly, with the sum taken over values of <code>x</code> that
037 * exceed the cutoff value.</p>
038 *
039 * <p>The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
040 * and the "variance direction" (upside or downside) defaults to downside.  The variance direction
041 * and bias correction may be set using property setters or their values can provided as
042 * parameters to {@link #evaluate(double[], double, Direction, boolean, int, int)}.</p>
043 *
044 * <p>If the input array is null, <code>evaluate</code> methods throw
045 * <code>IllegalArgumentException.</code>  If the array has length 1, <code>0</code>
046 * is returned, regardless of the value of the <code>cutoff.</code>
047 *
048 * <p><strong>Note that this class is not intended to be threadsafe.</strong> If
049 * multiple threads access an instance of this class concurrently, and one or
050 * more of these threads invoke property setters, external synchronization must
051 * be provided to ensure correct results.</p>
052 *
053 * @since 2.1
054 */
055public class SemiVariance extends AbstractUnivariateStatistic implements Serializable {
056
057    /**
058     * The UPSIDE Direction is used to specify that the observations above the
059     * cutoff point will be used to calculate SemiVariance.
060     */
061    public static final Direction UPSIDE_VARIANCE = Direction.UPSIDE;
062
063    /**
064     * The DOWNSIDE Direction is used to specify that the observations below
065     * the cutoff point will be used to calculate SemiVariance
066     */
067    public static final Direction DOWNSIDE_VARIANCE = Direction.DOWNSIDE;
068
069    /** Serializable version identifier */
070    private static final long serialVersionUID = 20150412L;
071
072    /**
073     * Determines whether or not bias correction is applied when computing the
074     * value of the statistic.  True means that bias is corrected.
075     */
076    private boolean biasCorrected = true;
077
078    /**
079     * Determines whether to calculate downside or upside SemiVariance.
080     */
081    private Direction varianceDirection = Direction.DOWNSIDE;
082
083    /**
084     * Constructs a SemiVariance with default (true) <code>biasCorrected</code>
085     * property and default (Downside) <code>varianceDirection</code> property.
086     */
087    public SemiVariance() {
088    }
089
090    /**
091     * Constructs a SemiVariance with the specified <code>biasCorrected</code>
092     * property and default (Downside) <code>varianceDirection</code> property.
093     *
094     * @param biasCorrected  setting for bias correction - true means
095     * bias will be corrected and is equivalent to using the "no arg"
096     * constructor
097     */
098    public SemiVariance(final boolean biasCorrected) {
099        this.biasCorrected = biasCorrected;
100    }
101
102    /**
103     * Constructs a SemiVariance with the specified <code>Direction</code> property
104     * and default (true) <code>biasCorrected</code> property
105     *
106     * @param direction  setting for the direction of the SemiVariance
107     * to calculate
108     */
109    public SemiVariance(final Direction direction) {
110        this.varianceDirection = direction;
111    }
112
113    /**
114     * Constructs a SemiVariance with the specified <code>isBiasCorrected</code>
115     * property and the specified <code>Direction</code> property.
116     *
117     * @param corrected  setting for bias correction - true means
118     * bias will be corrected and is equivalent to using the "no arg"
119     * constructor
120     *
121     * @param direction  setting for the direction of the SemiVariance
122     * to calculate
123     */
124    public SemiVariance(final boolean corrected, final Direction direction) {
125        this.biasCorrected = corrected;
126        this.varianceDirection = direction;
127    }
128
129    /**
130     * Copy constructor, creates a new {@code SemiVariance} identical
131     * to the {@code original}.
132     *
133     * @param original the {@code SemiVariance} instance to copy
134     * @throws NullArgumentException  if original is null
135     */
136    public SemiVariance(final SemiVariance original) throws NullArgumentException {
137        copy(original, this);
138    }
139
140    /**
141     * {@inheritDoc}
142     */
143    @Override
144    public SemiVariance copy() {
145        SemiVariance result = new SemiVariance();
146        // No try-catch or advertised exception because args are guaranteed non-null
147        copy(this, result);
148        return result;
149    }
150
151    /**
152     * Copies source to dest.
153     * <p>Neither source nor dest can be null.</p>
154     *
155     * @param source SemiVariance to copy
156     * @param dest SemiVariance to copy to
157     * @throws NullArgumentException if either source or dest is null
158     */
159    public static void copy(final SemiVariance source, SemiVariance dest)
160        throws NullArgumentException {
161        MathUtils.checkNotNull(source);
162        MathUtils.checkNotNull(dest);
163        dest.biasCorrected = source.biasCorrected;
164        dest.varianceDirection = source.varianceDirection;
165    }
166
167    /**
168     * <p>Returns the {@link SemiVariance} of the designated values against the mean, using
169     * instance properties varianceDirection and biasCorrection.</p>
170     *
171     * <p>Returns <code>NaN</code> if the array is empty and throws
172     * <code>IllegalArgumentException</code> if the array is null.</p>
173     *
174     * @param values the input array
175     * @param start index of the first array element to include
176     * @param length the number of elements to include
177     * @return the SemiVariance
178     * @throws MathIllegalArgumentException if the parameters are not valid
179     *
180     */
181     @Override
182     public double evaluate(final double[] values, final int start, final int length)
183         throws MathIllegalArgumentException {
184         double m = (new Mean()).evaluate(values, start, length);
185         return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length);
186     }
187
188     /**
189      * This method calculates {@link SemiVariance} for the entire array against the mean, using
190      * the current value of the biasCorrection instance property.
191      *
192      * @param values the input array
193      * @param direction the {@link Direction} of the semivariance
194      * @return the SemiVariance
195      * @throws MathIllegalArgumentException if values is null
196      *
197      */
198     public double evaluate(final double[] values, Direction direction)
199         throws MathIllegalArgumentException {
200         double m = (new Mean()).evaluate(values);
201         return evaluate(values, m, direction, biasCorrected, 0, values.length);
202     }
203
204     /**
205      * <p>Returns the {@link SemiVariance} of the designated values against the cutoff, using
206      * instance properties variancDirection and biasCorrection.</p>
207      *
208      * <p>Returns <code>NaN</code> if the array is empty and throws
209      * <code>MathIllegalArgumentException</code> if the array is null.</p>
210      *
211      * @param values the input array
212      * @param cutoff the reference point
213      * @return the SemiVariance
214      * @throws MathIllegalArgumentException if values is null
215      */
216     public double evaluate(final double[] values, final double cutoff)
217         throws MathIllegalArgumentException {
218         return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
219     }
220
221     /**
222      * <p>Returns the {@link SemiVariance} of the designated values against the cutoff in the
223      * given direction, using the current value of the biasCorrection instance property.</p>
224      *
225      * <p>Returns <code>NaN</code> if the array is empty and throws
226      * <code>MathIllegalArgumentException</code> if the array is null.</p>
227      *
228      * @param values the input array
229      * @param cutoff the reference point
230      * @param direction the {@link Direction} of the semivariance
231      * @return the SemiVariance
232      * @throws MathIllegalArgumentException if values is null
233      */
234     public double evaluate(final double[] values, final double cutoff, final Direction direction)
235         throws MathIllegalArgumentException {
236         return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
237     }
238
239     /**
240      * <p>Returns the {@link SemiVariance} of the designated values against the cutoff
241      * in the given direction with the provided bias correction.</p>
242      *
243      * <p>Returns <code>NaN</code> if the array is empty and throws
244      * <code>IllegalArgumentException</code> if the array is null.</p>
245      *
246      * @param values the input array
247      * @param cutoff the reference point
248      * @param direction the {@link Direction} of the semivariance
249      * @param corrected the BiasCorrection flag
250      * @param start index of the first array element to include
251      * @param length the number of elements to include
252      * @return the SemiVariance
253      * @throws MathIllegalArgumentException if the parameters are not valid
254      *
255      */
256     public double evaluate (final double[] values, final double cutoff, final Direction direction,
257                             final boolean corrected, final int start, final int length)
258         throws MathIllegalArgumentException {
259
260         MathArrays.verifyValues(values, start, length);
261         if (values.length == 0) {
262             return Double.NaN;
263         } else {
264             if (values.length == 1) {
265                 return 0.0;
266             } else {
267                 final boolean booleanDirection = direction.getDirection();
268
269                 double dev = 0.0;
270                 double sumsq = 0.0;
271                 for (int i = start; i < length; i++) {
272                     if ((values[i] > cutoff) == booleanDirection) {
273                         dev = values[i] - cutoff;
274                         sumsq += dev * dev;
275                     }
276                 }
277
278                 if (corrected) {
279                     return sumsq / (length - 1.0);
280                 } else {
281                     return sumsq / length;
282                 }
283             }
284         }
285     }
286
287     /**
288      * Returns true iff biasCorrected property is set to true.
289      *
290      * @return the value of biasCorrected.
291      */
292     public boolean isBiasCorrected() {
293         return biasCorrected;
294     }
295
296     /**
297      * Sets the biasCorrected property.
298      *
299      * @param biasCorrected new biasCorrected property value
300      */
301     public void setBiasCorrected(boolean biasCorrected) {
302         this.biasCorrected = biasCorrected;
303     }
304
305     /**
306      * Returns the varianceDirection property.
307      *
308      * @return the varianceDirection
309      */
310     public Direction getVarianceDirection () {
311         return varianceDirection;
312     }
313
314     /**
315      * Sets the variance direction
316      *
317      * @param varianceDirection the direction of the semivariance
318      */
319     public void setVarianceDirection(Direction varianceDirection) {
320         this.varianceDirection = varianceDirection;
321     }
322
323     /**
324      * The direction of the semivariance - either upside or downside. The direction
325      * is represented by boolean, with true corresponding to UPSIDE semivariance.
326      */
327     public enum Direction {
328         /**
329          * The UPSIDE Direction is used to specify that the observations above the
330          * cutoff point will be used to calculate SemiVariance
331          */
332         UPSIDE (true),
333
334         /**
335          * The DOWNSIDE Direction is used to specify that the observations below
336          * the cutoff point will be used to calculate SemiVariance
337          */
338         DOWNSIDE (false);
339
340         /**
341          * boolean value  UPSIDE <-> true
342          */
343         private boolean direction;
344
345         /**
346          * Create a Direction with the given value.
347          *
348          * @param b boolean value representing the Direction. True corresponds to UPSIDE.
349          */
350         Direction (boolean b) {
351             direction = b;
352         }
353
354         /**
355          * Returns the value of this Direction. True corresponds to UPSIDE.
356          *
357          * @return true if direction is UPSIDE; false otherwise
358          */
359         boolean getDirection () {
360             return direction;
361         }
362     }
363}