1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 /*
19 * This is not the original file distributed by the Apache Software Foundation
20 * It has been modified by the Hipparchus project
21 */
22
23 package org.hipparchus.stat.descriptive.moment;
24
25 import java.io.Serializable;
26
27 import org.hipparchus.exception.MathIllegalArgumentException;
28 import org.hipparchus.exception.NullArgumentException;
29 import org.hipparchus.stat.StatUtils;
30 import org.hipparchus.stat.descriptive.AbstractUnivariateStatistic;
31 import org.hipparchus.util.MathArrays;
32
33 /**
34 * Computes the semivariance of a set of values with respect to a given cutoff value.
35 * <p>
36 * We define the <i>downside semivariance</i> of a set of values <code>x</code>
37 * against the <i>cutoff value</i> <code>cutoff</code> to be <br>
38 * <code>Σ (x[i] - target)<sup>2</sup> / df</code> <br>
39 * where the sum is taken over all <code>i</code> such that <code>x[i] < cutoff</code>
40 * and <code>df</code> is the length of <code>x</code> (non-bias-corrected) or
41 * one less than this number (bias corrected). The <i>upside semivariance</i>
42 * is defined similarly, with the sum taken over values of <code>x</code> that
43 * exceed the cutoff value.
44 * <p>
45 * The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
46 * and the "variance direction" (upside or downside) defaults to downside. The variance direction
47 * and bias correction may be set using property setters or their values can provided as
48 * parameters to {@link #evaluate(double[], double, Direction, boolean, int, int)}.
49 * <p>
50 * If the input array is null, <code>evaluate</code> methods throw
51 * <code>IllegalArgumentException.</code> If the array has length 1, <code>0</code>
52 * is returned, regardless of the value of the <code>cutoff.</code>
53 * <p>
54 * <strong>Note that this class is not intended to be threadsafe.</strong> If
55 * multiple threads access an instance of this class concurrently, and one or
56 * more of these threads invoke property setters, external synchronization must
57 * be provided to ensure correct results.
58 */
59 public class SemiVariance extends AbstractUnivariateStatistic implements Serializable {
60
61 /**
62 * The UPSIDE Direction is used to specify that the observations above the
63 * cutoff point will be used to calculate SemiVariance.
64 */
65 public static final Direction UPSIDE_VARIANCE = Direction.UPSIDE;
66
67 /**
68 * The DOWNSIDE Direction is used to specify that the observations below
69 * the cutoff point will be used to calculate SemiVariance
70 */
71 public static final Direction DOWNSIDE_VARIANCE = Direction.DOWNSIDE;
72
73 /** Serializable version identifier */
74 private static final long serialVersionUID = 20150412L;
75
76 /**
77 * Determines whether or not bias correction is applied when computing the
78 * value of the statistic. True means that bias is corrected.
79 */
80 private final boolean biasCorrected;
81
82 /**
83 * Determines whether to calculate downside or upside SemiVariance.
84 */
85 private final Direction varianceDirection;
86
87 /**
88 * Constructs a SemiVariance with default (true) <code>biasCorrected</code>
89 * property and default (Downside) <code>varianceDirection</code> property.
90 */
91 public SemiVariance() {
92 this(true, Direction.DOWNSIDE);
93 }
94
95 /**
96 * Constructs a SemiVariance with the specified <code>biasCorrected</code>
97 * property and default (Downside) <code>varianceDirection</code> property.
98 *
99 * @param biasCorrected setting for bias correction - true means
100 * bias will be corrected and is equivalent to using the argumentless
101 * constructor
102 */
103 public SemiVariance(final boolean biasCorrected) {
104 this(biasCorrected, Direction.DOWNSIDE);
105 }
106
107 /**
108 * Constructs a SemiVariance with the specified <code>Direction</code> property
109 * and default (true) <code>biasCorrected</code> property
110 *
111 * @param direction setting for the direction of the SemiVariance
112 * to calculate
113 */
114 public SemiVariance(final Direction direction) {
115 this(true, direction);
116 }
117
118 /**
119 * Constructs a SemiVariance with the specified <code>isBiasCorrected</code>
120 * property and the specified <code>Direction</code> property.
121 *
122 * @param corrected setting for bias correction - true means
123 * bias will be corrected and is equivalent to using the argumentless
124 * constructor
125 *
126 * @param direction setting for the direction of the SemiVariance
127 * to calculate
128 */
129 public SemiVariance(final boolean corrected, final Direction direction) {
130 this.biasCorrected = corrected;
131 this.varianceDirection = direction;
132 }
133
134 /**
135 * Copy constructor, creates a new {@code SemiVariance} identical
136 * to the {@code original}.
137 *
138 * @param original the {@code SemiVariance} instance to copy
139 * @throws NullArgumentException if original is null
140 */
141 public SemiVariance(final SemiVariance original) throws NullArgumentException {
142 super(original);
143 this.biasCorrected = original.biasCorrected;
144 this.varianceDirection = original.varianceDirection;
145 }
146
147 /** {@inheritDoc} */
148 @Override
149 public SemiVariance copy() {
150 return new SemiVariance(this);
151 }
152
153 /**
154 * Returns the {@link SemiVariance} of the designated values against the mean, using
155 * instance properties varianceDirection and biasCorrection.
156 * <p>
157 * Returns <code>NaN</code> if the array is empty and throws
158 * <code>IllegalArgumentException</code> if the array is null.
159 *
160 * @param values the input array
161 * @param start index of the first array element to include
162 * @param length the number of elements to include
163 * @return the SemiVariance
164 * @throws MathIllegalArgumentException if the parameters are not valid
165 */
166 @Override
167 public double evaluate(final double[] values, final int start, final int length)
168 throws MathIllegalArgumentException {
169 double m = StatUtils.mean(values, start, length);
170 return evaluate(values, m, varianceDirection, biasCorrected, start, length);
171 }
172
173 /**
174 * This method calculates {@link SemiVariance} for the entire array against the mean,
175 * using the current value of the biasCorrection instance property.
176 *
177 * @param values the input array
178 * @param direction the {@link Direction} of the semivariance
179 * @return the SemiVariance
180 * @throws MathIllegalArgumentException if values is null
181 */
182 public double evaluate(final double[] values, Direction direction)
183 throws MathIllegalArgumentException {
184 double m = StatUtils.mean(values);
185 return evaluate(values, m, direction, biasCorrected, 0, values.length);
186 }
187
188 /**
189 * Returns the {@link SemiVariance} of the designated values against the cutoff,
190 * using instance properties variancDirection and biasCorrection.
191 * <p>
192 * Returns <code>NaN</code> if the array is empty.
193 *
194 * @param values the input array
195 * @param cutoff the reference point
196 * @return the SemiVariance
197 * @throws MathIllegalArgumentException if values is null
198 */
199 public double evaluate(final double[] values, final double cutoff)
200 throws MathIllegalArgumentException {
201 return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
202 }
203
204 /**
205 * Returns the {@link SemiVariance} of the designated values against the cutoff in the
206 * given direction, using the current value of the biasCorrection instance property.
207 * <p>
208 * Returns <code>NaN</code> if the array is empty.
209 *
210 * @param values the input array
211 * @param cutoff the reference point
212 * @param direction the {@link Direction} of the semivariance
213 * @return the SemiVariance
214 * @throws MathIllegalArgumentException if values is null
215 */
216 public double evaluate(final double[] values, final double cutoff, final Direction direction)
217 throws MathIllegalArgumentException {
218 return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
219 }
220
221 /**
222 * Returns the {@link SemiVariance} of the designated values against the cutoff
223 * in the given direction with the provided bias correction.
224 * <p>
225 * Returns <code>NaN</code> if the array is empty.
226 *
227 * @param values the input array
228 * @param cutoff the reference point
229 * @param direction the {@link Direction} of the semivariance
230 * @param corrected the BiasCorrection flag
231 * @param start index of the first array element to include
232 * @param length the number of elements to include
233 * @return the SemiVariance
234 * @throws MathIllegalArgumentException if the parameters are not valid
235 */
236 public double evaluate(final double[] values, final double cutoff, final Direction direction,
237 final boolean corrected, final int start, final int length)
238 throws MathIllegalArgumentException {
239
240 MathArrays.verifyValues(values, start, length);
241 if (values.length == 0) {
242 return Double.NaN;
243 } else {
244 if (values.length == 1) {
245 return 0.0;
246 } else {
247
248 double sumsq = 0.0;
249 final int end = start + length;
250 for (int i = start; i < end; i++) {
251 if (direction.considerObservation(values[i], cutoff)) {
252 final double dev = values[i] - cutoff;
253 sumsq += dev * dev;
254 }
255 }
256
257 if (corrected) {
258 return sumsq / (length - 1.0);
259 } else {
260 return sumsq / length;
261 }
262 }
263 }
264 }
265
266 /**
267 * Returns true iff biasCorrected property is set to true.
268 *
269 * @return the value of biasCorrected.
270 */
271 public boolean isBiasCorrected() {
272 return biasCorrected;
273 }
274
275 /**
276 * Returns a copy of this instance with the given biasCorrected setting.
277 *
278 * @param isBiasCorrected new biasCorrected property value
279 * @return a copy of this instance with the given bias correction setting
280 */
281 public SemiVariance withBiasCorrected(boolean isBiasCorrected) {
282 return new SemiVariance(isBiasCorrected, this.varianceDirection);
283 }
284
285 /**
286 * Returns the varianceDirection property.
287 *
288 * @return the varianceDirection
289 */
290 public Direction getVarianceDirection () {
291 return varianceDirection;
292 }
293
294 /**
295 * Returns a copy of this instance with the given direction setting.
296 *
297 * @param direction the direction of the semivariance
298 * @return a copy of this instance with the given direction setting
299 */
300 public SemiVariance withVarianceDirection(Direction direction) {
301 return new SemiVariance(this.biasCorrected, direction);
302 }
303
304 /**
305 * The direction of the semivariance - either upside or downside. The direction
306 * is represented by boolean, with true corresponding to UPSIDE semivariance.
307 */
308 public enum Direction {
309 /**
310 * The UPSIDE Direction is used to specify that the observations above the
311 * cutoff point will be used to calculate SemiVariance
312 */
313 UPSIDE (true),
314
315 /**
316 * The DOWNSIDE Direction is used to specify that the observations below
317 * the cutoff point will be used to calculate SemiVariance
318 */
319 DOWNSIDE (false);
320
321 /**
322 * boolean value UPSIDE <-> true
323 */
324 private final boolean direction;
325
326 /**
327 * Create a Direction with the given value.
328 *
329 * @param b boolean value representing the Direction. True corresponds to UPSIDE.
330 */
331 Direction (boolean b) {
332 direction = b;
333 }
334
335 /** Check if observation should be considered.
336 * @param value observation value
337 * @param cutoff cutoff point
338 * @return true if observation should be considered.
339 * @since 1.4
340 */
341 boolean considerObservation(final double value, final double cutoff) {
342 return value > cutoff == direction;
343 }
344
345 }
346 }