    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
    018    package org.apache.commons.math3.stat.descriptive.moment;
    019
    020    import java.io.Serializable;
    021
    022    import org.apache.commons.math3.exception.MathIllegalArgumentException;
    023    import org.apache.commons.math3.exception.NullArgumentException;
    024    import org.apache.commons.math3.stat.descriptive.AbstractUnivariateStatistic;
    025    import 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     * @version $Id: SemiVariance.java 1385386 2012-09-16 22:11:15Z psteitz $
    054     */
    055    public 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 = -2653430366886024994L;
    071
    072        /**
    073         * Determines whether or not bias correction is applied when computing the
    074         * value of the statisic.  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 argumentless
    096         * constructor
    097         */
    098        public SemiVariance(final boolean biasCorrected) {
    099            this.biasCorrected = biasCorrected;
    100        }
    101
    102
    103        /**
    104         * Constructs a SemiVariance with the specified <code>Direction</code> property
    105         * and default (true) <code>biasCorrected</code> property
    106         *
    107         * @param direction  setting for the direction of the SemiVariance
    108         * to calculate
    109         */
    110        public SemiVariance(final Direction direction) {
    111            this.varianceDirection = direction;
    112        }
    113
    114
    115        /**
    116         * Constructs a SemiVariance with the specified <code>isBiasCorrected</code>
    117         * property and the specified <code>Direction</code> property.
    118         *
    119         * @param corrected  setting for bias correction - true means
    120         * bias will be corrected and is equivalent to using the argumentless
    121         * constructor
    122         *
    123         * @param direction  setting for the direction of the SemiVariance
    124         * to calculate
    125         */
    126        public SemiVariance(final boolean corrected, final Direction direction) {
    127            this.biasCorrected = corrected;
    128            this.varianceDirection = direction;
    129        }
    130
    131
    132        /**
    133         * Copy constructor, creates a new {@code SemiVariance} identical
    134         * to the {@code original}
    135         *
    136         * @param original the {@code SemiVariance} instance to copy
    137         * @throws NullArgumentException  if original is null
    138         */
    139        public SemiVariance(final SemiVariance original) throws NullArgumentException {
    140            copy(original, this);
    141        }
    142
    143
    144        /**
    145         * {@inheritDoc}
    146         */
    147        @Override
    148        public SemiVariance copy() {
    149            SemiVariance result = new SemiVariance();
    150            // No try-catch or advertised exception because args are guaranteed non-null
    151            copy(this, result);
    152            return result;
    153        }
    154
    155
    156        /**
    157         * Copies source to dest.
    158         * <p>Neither source nor dest can be null.</p>
    159         *
    160         * @param source SemiVariance to copy
    161         * @param dest SemiVariance to copy to
    162         * @throws NullArgumentException if either source or dest is null
    163         */
    164        public static void copy(final SemiVariance source, SemiVariance dest)
    165            throws NullArgumentException {
    166            MathUtils.checkNotNull(source);
    167            MathUtils.checkNotNull(dest);
    168            dest.setData(source.getDataRef());
    169            dest.biasCorrected = source.biasCorrected;
    170            dest.varianceDirection = source.varianceDirection;
    171        }
    172
    173        /**
    174          * <p>Returns the {@link SemiVariance} of the designated values against the mean, using
    175          * instance properties varianceDirection and biasCorrection.</p>
    176          *
    177          * <p>Returns <code>NaN</code> if the array is empty and throws
    178          * <code>IllegalArgumentException</code> if the array is null.</p>
    179          *
    180          * @param values the input array
    181          * @param start index of the first array element to include
    182          * @param length the number of elements to include
    183          * @return the SemiVariance
    184          * @throws MathIllegalArgumentException if the parameters are not valid
    185          *
    186          */
    187          @Override
    188          public double evaluate(final double[] values, final int start, final int length)
    189          throws MathIllegalArgumentException {
    190            double m = (new Mean()).evaluate(values, start, length);
    191            return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length);
    192          }
    193
    194
    195          /**
    196           * This method calculates {@link SemiVariance} for the entire array against the mean, using
    197           * the current value of the biasCorrection instance property.
    198           *
    199           * @param values the input array
    200           * @param direction the {@link Direction} of the semivariance
    201           * @return the SemiVariance
    202           * @throws MathIllegalArgumentException if values is null
    203           *
    204           */
    205          public double evaluate(final double[] values, Direction direction)
    206          throws MathIllegalArgumentException {
    207              double m = (new Mean()).evaluate(values);
    208              return evaluate (values, m, direction, biasCorrected, 0, values.length);
    209          }
    210
    211          /**
    212           * <p>Returns the {@link SemiVariance} of the designated values against the cutoff, using
    213           * instance properties variancDirection and biasCorrection.</p>
    214           *
    215           * <p>Returns <code>NaN</code> if the array is empty and throws
    216           * <code>MathIllegalArgumentException</code> if the array is null.</p>
    217           *
    218           * @param values the input array
    219           * @param cutoff the reference point
    220           * @return the SemiVariance
    221           * @throws MathIllegalArgumentException if values is null
    222           */
    223          public double evaluate(final double[] values, final double cutoff)
    224          throws MathIllegalArgumentException {
    225              return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
    226          }
    227
    228          /**
    229           * <p>Returns the {@link SemiVariance} of the designated values against the cutoff in the
    230           * given direction, using the current value of the biasCorrection instance property.</p>
    231           *
    232           * <p>Returns <code>NaN</code> if the array is empty and throws
    233           * <code>MathIllegalArgumentException</code> if the array is null.</p>
    234           *
    235           * @param values the input array
    236           * @param cutoff the reference point
    237           * @param direction the {@link Direction} of the semivariance
    238           * @return the SemiVariance
    239           * @throws MathIllegalArgumentException if values is null
    240           */
    241          public double evaluate(final double[] values, final double cutoff, final Direction direction)
    242          throws MathIllegalArgumentException {
    243              return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
    244          }
    245
    246
    247         /**
    248          * <p>Returns the {@link SemiVariance} of the designated values against the cutoff
    249          * in the given direction with the provided bias correction.</p>
    250          *
    251          * <p>Returns <code>NaN</code> if the array is empty and throws
    252          * <code>IllegalArgumentException</code> if the array is null.</p>
    253          *
    254          * @param values the input array
    255          * @param cutoff the reference point
    256          * @param direction the {@link Direction} of the semivariance
    257          * @param corrected the BiasCorrection flag
    258          * @param start index of the first array element to include
    259          * @param length the number of elements to include
    260          * @return the SemiVariance
    261          * @throws MathIllegalArgumentException if the parameters are not valid
    262          *
    263          */
    264        public double evaluate (final double[] values, final double cutoff, final Direction direction,
    265                final boolean corrected, final int start, final int length) throws MathIllegalArgumentException {
    266
    267            test(values, start, length);
    268            if (values.length == 0) {
    269                return Double.NaN;
    270            } else {
    271                if (values.length == 1) {
    272                    return 0.0;
    273                } else {
    274                    final boolean booleanDirection = direction.getDirection();
    275
    276                    double dev = 0.0;
    277                    double sumsq = 0.0;
    278                    for (int i = start; i < length; i++) {
    279                        if ((values[i] > cutoff) == booleanDirection) {
    280                           dev = values[i] - cutoff;
    281                           sumsq += dev * dev;
    282                        }
    283                    }
    284
    285                    if (corrected) {
    286                        return sumsq / (length - 1.0);
    287                    } else {
    288                        return sumsq / length;
    289                    }
    290                }
    291            }
    292        }
    293
    294        /**
    295         * Returns true iff biasCorrected property is set to true.
    296         *
    297         * @return the value of biasCorrected.
    298         */
    299        public boolean isBiasCorrected() {
    300            return biasCorrected;
    301        }
    302
    303        /**
    304         * Sets the biasCorrected property.
    305         *
    306         * @param biasCorrected new biasCorrected property value
    307         */
    308        public void setBiasCorrected(boolean biasCorrected) {
    309            this.biasCorrected = biasCorrected;
    310        }
    311
    312        /**
    313         * Returns the varianceDirection property.
    314         *
    315         * @return the varianceDirection
    316         */
    317        public Direction getVarianceDirection () {
    318            return varianceDirection;
    319        }
    320
    321        /**
    322         * Sets the variance direction
    323         *
    324         * @param varianceDirection the direction of the semivariance
    325         */
    326        public void setVarianceDirection(Direction varianceDirection) {
    327            this.varianceDirection = varianceDirection;
    328        }
    329
    330        /**
    331         * The direction of the semivariance - either upside or downside. The direction
    332         * is represented by boolean, with true corresponding to UPSIDE semivariance.
    333         */
    334        public enum Direction {
    335            /**
    336             * The UPSIDE Direction is used to specify that the observations above the
    337             * cutoff point will be used to calculate SemiVariance
    338             */
    339            UPSIDE (true),
    340
    341            /**
    342             * The DOWNSIDE Direction is used to specify that the observations below
    343             * the cutoff point will be used to calculate SemiVariance
    344             */
    345            DOWNSIDE (false);
    346
    347            /**
    348             *   boolean value  UPSIDE <-> true
    349             */
    350            private boolean direction;
    351
    352            /**
    353             * Create a Direction with the given value.
    354             *
    355             * @param b boolean value representing the Direction. True corresponds to UPSIDE.
    356             */
    357            Direction (boolean b) {
    358                direction = b;
    359            }
    360
    361            /**
    362             * Returns the value of this Direction. True corresponds to UPSIDE.
    363             *
    364             * @return true if direction is UPSIDE; false otherwise
    365             */
    366            boolean getDirection () {
    367                return direction;
    368            }
    369        }
    370    }