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