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 * http://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 package org.apache.commons.math4.legacy.stat.descriptive.moment;
19
20 import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
21 import org.apache.commons.math4.legacy.exception.NullArgumentException;
22 import org.apache.commons.math4.legacy.stat.descriptive.AbstractUnivariateStatistic;
23 import org.apache.commons.math4.legacy.core.MathArrays;
24
25 /**
26 * <p>Computes the semivariance of a set of values with respect to a given cutoff value.
27 * We define the <i>downside semivariance</i> of a set of values <code>x</code>
28 * against the <i>cutoff value</i> <code>cutoff</code> to be <br>
29 * <code>Σ (x[i] - target)<sup>2</sup> / df</code> <br>
30 * where the sum is taken over all <code>i</code> such that {@code x[i] < cutoff}
31 * and <code>df</code> is the length of <code>x</code> (non-bias-corrected) or
32 * one less than this number (bias corrected). The <i>upside semivariance</i>
33 * is defined similarly, with the sum taken over values of <code>x</code> that
34 * exceed the cutoff value.</p>
35 *
36 * <p>The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
37 * and the "variance direction" (upside or downside) defaults to downside. The variance direction
38 * and bias correction may be set using property setters or their values can provided as
39 * parameters to {@link #evaluate(double[], double, Direction, boolean, int, int)}.</p>
40 *
41 * <p>If the input array is null, <code>evaluate</code> methods throw
42 * <code>IllegalArgumentException.</code> If the array has length 1, <code>0</code>
43 * is returned, regardless of the value of the <code>cutoff.</code>
44 *
45 * <p><strong>Note that this class is not intended to be threadsafe.</strong> If
46 * multiple threads access an instance of this class concurrently, and one or
47 * more of these threads invoke property setters, external synchronization must
48 * be provided to ensure correct results.</p>
49 *
50 * @since 2.1
51 */
52 public class SemiVariance extends AbstractUnivariateStatistic {
53
54 /**
55 * The UPSIDE Direction is used to specify that the observations above the
56 * cutoff point will be used to calculate SemiVariance.
57 */
58 public static final Direction UPSIDE_VARIANCE = Direction.UPSIDE;
59
60 /**
61 * The DOWNSIDE Direction is used to specify that the observations below.
62 * the cutoff point will be used to calculate SemiVariance
63 */
64 public static final Direction DOWNSIDE_VARIANCE = Direction.DOWNSIDE;
65
66 /**
67 * Determines whether or not bias correction is applied when computing the
68 * value of the statistic. True means that bias is corrected.
69 */
70 private boolean biasCorrected = true;
71
72 /**
73 * Determines whether to calculate downside or upside SemiVariance.
74 */
75 private Direction varianceDirection = Direction.DOWNSIDE;
76
77 /**
78 * Constructs a SemiVariance with default (true) <code>biasCorrected</code>
79 * property and default (Downside) <code>varianceDirection</code> property.
80 */
81 public SemiVariance() {
82 }
83
84 /**
85 * Constructs a SemiVariance with the specified <code>biasCorrected</code>
86 * property and default (Downside) <code>varianceDirection</code> property.
87 *
88 * @param biasCorrected setting for bias correction - true means
89 * bias will be corrected and is equivalent to using the "no arg"
90 * constructor
91 */
92 public SemiVariance(final boolean biasCorrected) {
93 this.biasCorrected = biasCorrected;
94 }
95
96 /**
97 * Constructs a SemiVariance with the specified <code>Direction</code> property.
98 * and default (true) <code>biasCorrected</code> property
99 *
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 }