    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.math.stat.descriptive.moment;
    019
    020    import java.io.Serializable;
    021    import org.apache.commons.math.exception.NullArgumentException;
    022    import org.apache.commons.math.exception.util.LocalizedFormats;
    023    import org.apache.commons.math.stat.descriptive.AbstractUnivariateStatistic;
    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</code>
    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     * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
    051     * @since 2.1
    052     */
    053
    054    public 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         */
    137        public SemiVariance(final SemiVariance original) {
    138            copy(original, this);
    139        }
    140
    141
    142        /**
    143         * {@inheritDoc}
    144         */
    145        @Override
    146        public SemiVariance copy() {
    147            SemiVariance result = new SemiVariance();
    148            copy(this, result);
    149            return result;
    150        }
    151
    152
    153        /**
    154         * Copies source to dest.
    155         * <p>Neither source nor dest can be null.</p>
    156         *
    157         * @param source SemiVariance to copy
    158         * @param dest SemiVariance to copy to
    159         * @throws NullPointerException if either source or dest is null
    160         */
    161        public static void copy(final SemiVariance source, SemiVariance dest) {
    162            dest.setData(source.getDataRef());
    163            dest.biasCorrected = source.biasCorrected;
    164            dest.varianceDirection = source.varianceDirection;
    165        }
    166
    167
    168        /**
    169         * This method calculates {@link SemiVariance} for the entire array against the mean, using
    170         * instance properties varianceDirection and biasCorrection.
    171         *
    172         * @param values the input array
    173         * @return the SemiVariance
    174         * @throws IllegalArgumentException if values is null
    175         *
    176         */
    177        @Override
    178        public double evaluate(final double[] values) {
    179            if (values == null) {
    180                throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
    181             }
    182            return evaluate(values, 0, values.length);
    183        }
    184
    185
    186        /**
    187          * <p>Returns the {@link SemiVariance} of the designated values against the mean, using
    188          * instance properties varianceDirection and biasCorrection.</p>
    189          *
    190          * <p>Returns <code>NaN</code> if the array is empty and throws
    191          * <code>IllegalArgumentException</code> if the array is null.</p>
    192          *
    193          * @param values the input array
    194          * @param start index of the first array element to include
    195          * @param length the number of elements to include
    196          * @return the SemiVariance
    197          * @throws IllegalArgumentException if the parameters are not valid
    198          *
    199          */
    200          @Override
    201          public double evaluate(final double[] values, final int start, final int length) {
    202            double m = (new Mean()).evaluate(values, start, length);
    203            return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length);
    204          }
    205
    206
    207          /**
    208           * This method calculates {@link SemiVariance} for the entire array against the mean, using
    209           * the current value of the biasCorrection instance property.
    210           *
    211           * @param values the input array
    212           * @param direction the {@link Direction} of the semivariance
    213           * @return the SemiVariance
    214           * @throws IllegalArgumentException if values is null
    215           *
    216           */
    217          public double evaluate(final double[] values, Direction direction) {
    218              double m = (new Mean()).evaluate(values);
    219              return evaluate (values, m, direction, biasCorrected, 0, values.length);
    220          }
    221
    222          /**
    223           * <p>Returns the {@link SemiVariance} of the designated values against the cutoff, using
    224           * instance properties variancDirection and biasCorrection.</p>
    225           *
    226           * <p>Returns <code>NaN</code> if the array is empty and throws
    227           * <code>IllegalArgumentException</code> if the array is null.</p>
    228           *
    229           * @param values the input array
    230           * @param cutoff the reference point
    231           * @return the SemiVariance
    232           * @throws IllegalArgumentException if values is null
    233           */
    234          public double evaluate(final double[] values, final double cutoff) {
    235              return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
    236          }
    237
    238          /**
    239           * <p>Returns the {@link SemiVariance} of the designated values against the cutoff in the
    240           * given direction, using the current value of the biasCorrection instance property.</p>
    241           *
    242           * <p>Returns <code>NaN</code> if the array is empty and throws
    243           * <code>IllegalArgumentException</code> if the array is null.</p>
    244           *
    245           * @param values the input array
    246           * @param cutoff the reference point
    247           * @param direction the {@link Direction} of the semivariance
    248           * @return the SemiVariance
    249           * @throws IllegalArgumentException if values is null
    250           */
    251          public double evaluate(final double[] values, final double cutoff, final Direction direction) {
    252              return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
    253          }
    254
    255
    256         /**
    257          * <p>Returns the {@link SemiVariance} of the designated values against the cutoff
    258          * in the given direction with the provided bias correction.</p>
    259          *
    260          * <p>Returns <code>NaN</code> if the array is empty and throws
    261          * <code>IllegalArgumentException</code> if the array is null.</p>
    262          *
    263          * @param values the input array
    264          * @param cutoff the reference point
    265          * @param direction the {@link Direction} of the semivariance
    266          * @param corrected the BiasCorrection flag
    267          * @param start index of the first array element to include
    268          * @param length the number of elements to include
    269          * @return the SemiVariance
    270          * @throws IllegalArgumentException if the parameters are not valid
    271          *
    272          */
    273        public double evaluate (final double[] values, final double cutoff, final Direction direction,
    274                final boolean corrected, final int start, final int length) {
    275
    276            test(values, start, length);
    277            if (values.length == 0) {
    278                return Double.NaN;
    279            } else {
    280                if (values.length == 1) {
    281                    return 0.0;
    282                } else {
    283                    final boolean booleanDirection = direction.getDirection();
    284
    285                    double dev = 0.0;
    286                    double sumsq = 0.0;
    287                    for (int i = start; i < length; i++) {
    288                        if ((values[i] > cutoff) == booleanDirection) {
    289                           dev = values[i] - cutoff;
    290                           sumsq += dev * dev;
    291                        }
    292                    }
    293
    294                    if (corrected) {
    295                        return sumsq / (length - 1.0);
    296                    } else {
    297                        return sumsq / length;
    298                    }
    299                }
    300            }
    301        }
    302
    303        /**
    304         * Returns true iff biasCorrected property is set to true.
    305         *
    306         * @return the value of biasCorrected.
    307         */
    308        public boolean isBiasCorrected() {
    309            return biasCorrected;
    310        }
    311
    312        /**
    313         * Sets the biasCorrected property.
    314         *
    315         * @param biasCorrected new biasCorrected property value
    316         */
    317        public void setBiasCorrected(boolean biasCorrected) {
    318            this.biasCorrected = biasCorrected;
    319        }
    320
    321        /**
    322         * Returns the varianceDirection property.
    323         *
    324         * @return the varianceDirection
    325         */
    326        public Direction getVarianceDirection () {
    327            return varianceDirection;
    328        }
    329
    330        /**
    331         * Sets the variance direction
    332         *
    333         * @param varianceDirection the direction of the semivariance
    334         */
    335        public void setVarianceDirection(Direction varianceDirection) {
    336            this.varianceDirection = varianceDirection;
    337        }
    338
    339        /**
    340         * The direction of the semivariance - either upside or downside. The direction
    341         * is represented by boolean, with true corresponding to UPSIDE semivariance.
    342         */
    343        public enum Direction {
    344            /**
    345             * The UPSIDE Direction is used to specify that the observations above the
    346             * cutoff point will be used to calculate SemiVariance
    347             */
    348            UPSIDE (true),
    349
    350            /**
    351             * The DOWNSIDE Direction is used to specify that the observations below
    352             * the cutoff point will be used to calculate SemiVariance
    353             */
    354            DOWNSIDE (false);
    355
    356            /**
    357             *   boolean value  UPSIDE <-> true
    358             */
    359            private boolean direction;
    360
    361            /**
    362             * Create a Direction with the given value.
    363             *
    364             * @param b boolean value representing the Direction. True corresponds to UPSIDE.
    365             */
    366            Direction (boolean b) {
    367                direction = b;
    368            }
    369
    370            /**
    371             * Returns the value of this Direction. True corresponds to UPSIDE.
    372             *
    373             * @return true if direction is UPSIDE; false otherwise
    374             */
    375            boolean getDirection () {
    376                return direction;
    377            }
    378        }
    379    }