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.legacy.stat.descriptive.moment;
019
020import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
021import org.apache.commons.math4.legacy.exception.NullArgumentException;
022import org.apache.commons.math4.legacy.stat.descriptive.AbstractUnivariateStatistic;
023import org.apache.commons.math4.legacy.core.MathArrays;
024
025/**
026 * <p>Computes the semivariance of a set of values with respect to a given cutoff value.
027 * We define the <i>downside semivariance</i> of a set of values <code>x</code>
028 * against the <i>cutoff value</i> <code>cutoff</code> to be <br>
029 * <code>&Sigma; (x[i] - target)<sup>2</sup> / df</code> <br>
030 * where the sum is taken over all <code>i</code> such that {@code x[i] < cutoff}
031 * and <code>df</code> is the length of <code>x</code> (non-bias-corrected) or
032 * one less than this number (bias corrected).  The <i>upside semivariance</i>
033 * is defined similarly, with the sum taken over values of <code>x</code> that
034 * exceed the cutoff value.</p>
035 *
036 * <p>The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
037 * and the "variance direction" (upside or downside) defaults to downside.  The variance direction
038 * and bias correction may be set using property setters or their values can provided as
039 * parameters to {@link #evaluate(double[], double, Direction, boolean, int, int)}.</p>
040 *
041 * <p>If the input array is null, <code>evaluate</code> methods throw
042 * <code>IllegalArgumentException.</code>  If the array has length 1, <code>0</code>
043 * is returned, regardless of the value of the <code>cutoff.</code>
044 *
045 * <p><strong>Note that this class is not intended to be threadsafe.</strong> If
046 * multiple threads access an instance of this class concurrently, and one or
047 * more of these threads invoke property setters, external synchronization must
048 * be provided to ensure correct results.</p>
049 *
050 * @since 2.1
051 */
052public class SemiVariance extends AbstractUnivariateStatistic {
053
054    /**
055     * The UPSIDE Direction is used to specify that the observations above the
056     * cutoff point will be used to calculate SemiVariance.
057     */
058    public static final Direction UPSIDE_VARIANCE = Direction.UPSIDE;
059
060    /**
061     * The DOWNSIDE Direction is used to specify that the observations below.
062     * the cutoff point will be used to calculate SemiVariance
063     */
064    public static final Direction DOWNSIDE_VARIANCE = Direction.DOWNSIDE;
065
066    /**
067     * Determines whether or not bias correction is applied when computing the
068     * value of the statistic.  True means that bias is corrected.
069     */
070    private boolean biasCorrected = true;
071
072    /**
073     * Determines whether to calculate downside or upside SemiVariance.
074     */
075    private Direction varianceDirection = Direction.DOWNSIDE;
076
077    /**
078     * Constructs a SemiVariance with default (true) <code>biasCorrected</code>
079     * property and default (Downside) <code>varianceDirection</code> property.
080     */
081    public SemiVariance() {
082    }
083
084    /**
085     * Constructs a SemiVariance with the specified <code>biasCorrected</code>
086     * property and default (Downside) <code>varianceDirection</code> property.
087     *
088     * @param biasCorrected  setting for bias correction - true means
089     * bias will be corrected and is equivalent to using the "no arg"
090     * constructor
091     */
092    public SemiVariance(final boolean biasCorrected) {
093        this.biasCorrected = biasCorrected;
094    }
095
096    /**
097     * Constructs a SemiVariance with the specified <code>Direction</code> property.
098     * and default (true) <code>biasCorrected</code> property
099     *
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}