Skip to content

Commit dba20b8

Browse files
committed
MATH-1671: Reduce the method overrides for the SemiVariance
The SemiVariance had many untested methods and the implementation was bugged. This change corrects the implementation: - from using (i=start; i<length; i++) to use i<start+length - the use of arguments 0 and values.length to start and length for the array sub-range method Tests have been added for sub-range evaluation and to complete code coverage for the class.
1 parent 0a0823b commit dba20b8

File tree

9 files changed

+178
-204
lines changed

9 files changed

+178
-204
lines changed

commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/StatUtils.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,6 @@ public static double mean(final double[] values) throws MathIllegalArgumentExcep
228228
* is empty.
229229
* <p>
230230
* Throws <code>IllegalArgumentException</code> if the array is null.
231-
* <p>
232-
* See {@link org.apache.commons.math4.legacy.stat.descriptive.moment.Mean Mean} for
233-
* details on the computing algorithm.
234231
*
235232
* @param values the input array
236233
* @param begin index of the first array element to include

commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/descriptive/moment/SemiVariance.java

Lines changed: 43 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
2121
import org.apache.commons.math4.legacy.exception.NullArgumentException;
22+
import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
2223
import org.apache.commons.math4.legacy.stat.descriptive.AbstractUnivariateStatistic;
2324
import org.apache.commons.math4.legacy.core.MathArrays;
2425

@@ -35,8 +36,7 @@
3536
*
3637
* <p>The cutoff value defaults to the mean, bias correction defaults to <code>true</code>
3738
* 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>
39+
* and bias correction may be set using property setters.</p>
4040
*
4141
* <p>If the input array is null, <code>evaluate</code> methods throw
4242
* <code>IllegalArgumentException.</code> If the array has length 1, <code>0</code>
@@ -51,18 +51,6 @@
5151
*/
5252
public class SemiVariance extends AbstractUnivariateStatistic {
5353

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-
6654
/**
6755
* Determines whether or not bias correction is applied when computing the
6856
* value of the statistic. True means that bias is corrected.
@@ -79,56 +67,7 @@ public class SemiVariance extends AbstractUnivariateStatistic {
7967
* property and default (Downside) <code>varianceDirection</code> property.
8068
*/
8169
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);
70+
// Do nothing
13271
}
13372

13473
/**
@@ -137,27 +76,11 @@ public SemiVariance(final SemiVariance original) throws NullArgumentException {
13776
@Override
13877
public SemiVariance copy() {
13978
SemiVariance result = new SemiVariance();
140-
// No try-catch or advertised exception because args are guaranteed non-null
141-
copy(this, result);
79+
result.biasCorrected = biasCorrected;
80+
result.varianceDirection = varianceDirection;
14281
return result;
14382
}
14483

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-
16184
/**
16285
* <p>Returns the {@link SemiVariance} of the designated values against the mean, using
16386
* instance properties varianceDirection and biasCorrection.</p>
@@ -177,24 +100,7 @@ public double evaluate(final double[] values, final int start, final int length)
177100
throws MathIllegalArgumentException {
178101
MathArrays.verifyValues(values, start, length);
179102
double m = org.apache.commons.statistics.descriptive.Mean.ofRange(values, start, start + length).getAsDouble();
180-
return evaluate(values, m, varianceDirection, biasCorrected, 0, values.length);
181-
}
182-
183-
/**
184-
* This method calculates {@link SemiVariance} for the entire array against the mean, using
185-
* the current value of the biasCorrection instance property.
186-
*
187-
* @param values the input array
188-
* @param direction the {@link Direction} of the semivariance
189-
* @return the SemiVariance
190-
* @throws MathIllegalArgumentException if values is null
191-
*
192-
*/
193-
public double evaluate(final double[] values, Direction direction)
194-
throws MathIllegalArgumentException {
195-
MathArrays.verifyValues(values, 0, 0);
196-
double m = org.apache.commons.statistics.descriptive.Mean.of(values).getAsDouble();
197-
return evaluate(values, m, direction, biasCorrected, 0, values.length);
103+
return compute(values, m, varianceDirection, biasCorrected, start, length);
198104
}
199105

200106
/**
@@ -211,33 +117,38 @@ public double evaluate(final double[] values, Direction direction)
211117
*/
212118
public double evaluate(final double[] values, final double cutoff)
213119
throws MathIllegalArgumentException {
214-
return evaluate(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
120+
if (values == null) {
121+
throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
122+
}
123+
return compute(values, cutoff, varianceDirection, biasCorrected, 0, values.length);
215124
}
216125

217126
/**
218-
* <p>Returns the {@link SemiVariance} of the designated values against the cutoff in the
219-
* given direction, using the current value of the biasCorrection instance property.</p>
127+
* <p>Returns the {@link SemiVariance} of the designated values against the cutoff
128+
* in the given direction with the provided bias correction.</p>
220129
*
221130
* <p>Returns <code>NaN</code> if the array is empty and throws
222-
* <code>MathIllegalArgumentException</code> if the array is null.</p>
131+
* <code>IllegalArgumentException</code> if the array is null.</p>
223132
*
224133
* @param values the input array
225134
* @param cutoff the reference point
226-
* @param direction the {@link Direction} of the semivariance
135+
* @param start index of the first array element to include
136+
* @param length the number of elements to include
227137
* @return the SemiVariance
228-
* @throws MathIllegalArgumentException if values is null
138+
* @throws MathIllegalArgumentException if the parameters are not valid
139+
*
229140
*/
230-
public double evaluate(final double[] values, final double cutoff, final Direction direction)
141+
public double evaluate(final double[] values, final double cutoff, final int start, final int length)
231142
throws MathIllegalArgumentException {
232-
return evaluate(values, cutoff, direction, biasCorrected, 0, values.length);
143+
MathArrays.verifyValues(values, start, length);
144+
return compute(values, cutoff, varianceDirection, biasCorrected, start, length);
233145
}
234146

235147
/**
236148
* <p>Returns the {@link SemiVariance} of the designated values against the cutoff
237149
* in the given direction with the provided bias correction.</p>
238150
*
239-
* <p>Returns <code>NaN</code> if the array is empty and throws
240-
* <code>IllegalArgumentException</code> if the array is null.</p>
151+
* <p>Returns <code>NaN</code> if the array is empty.</p>
241152
*
242153
* @param values the input array
243154
* @param cutoff the reference point
@@ -246,38 +157,33 @@ public double evaluate(final double[] values, final double cutoff, final Directi
246157
* @param start index of the first array element to include
247158
* @param length the number of elements to include
248159
* @return the SemiVariance
249-
* @throws MathIllegalArgumentException if the parameters are not valid
250-
*
251160
*/
252-
public double evaluate (final double[] values, final double cutoff, final Direction direction,
253-
final boolean corrected, final int start, final int length)
254-
throws MathIllegalArgumentException {
255-
256-
MathArrays.verifyValues(values, start, length);
257-
if (values.length == 0) {
161+
private static double compute(double[] values, double cutoff, Direction direction,
162+
boolean corrected, int start, int length) {
163+
// Arguments must have been validated
164+
if (length == 0) {
258165
return Double.NaN;
259-
} else {
260-
if (values.length == 1) {
261-
return 0.0;
262-
} else {
263-
final boolean booleanDirection = direction.getDirection();
264-
265-
double dev = 0.0;
266-
double sumsq = 0.0;
267-
for (int i = start; i < length; i++) {
268-
if ((values[i] > cutoff) == booleanDirection) {
269-
dev = values[i] - cutoff;
270-
sumsq += dev * dev;
271-
}
272-
}
166+
}
167+
if (length == 1) {
168+
return 0.0;
169+
}
170+
final boolean booleanDirection = direction.getDirection();
273171

274-
if (corrected) {
275-
return sumsq / (length - 1.0);
276-
} else {
277-
return sumsq / length;
278-
}
172+
double dev = 0.0;
173+
double sumsq = 0.0;
174+
final int end = start + length;
175+
for (int i = start; i < end; i++) {
176+
if ((values[i] > cutoff) == booleanDirection) {
177+
dev = values[i] - cutoff;
178+
sumsq += dev * dev;
279179
}
280180
}
181+
182+
if (corrected) {
183+
return sumsq / (length - 1.0);
184+
} else {
185+
return sumsq / length;
186+
}
281187
}
282188

283189
/**

commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/descriptive/moment/Mean.java renamed to commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/descriptive/moment/WeightedMean.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,11 @@
2121
import org.apache.commons.math4.legacy.stat.descriptive.WeightedEvaluation;
2222

2323
/**
24-
* Computes the arithmetic mean of a set of values. Uses the definitional
25-
* formula:
24+
* Computes the weighted mean of a set of values. Uses the formula:
2625
* <p>
27-
* mean = sum(x_i) / n
26+
* mean = sum(w_i * x_i) / sum(w_i)
2827
* </p>
29-
* <p>where <code>n</code> is the number of observations.
28+
* <p>where <code>w_i</code> is the weight for observation <code>x_i</code>.
3029
* </p>
3130
* <p> If used to compute the mean of an array
3231
* of stored values, a two-pass, corrected algorithm is used, starting with
@@ -41,21 +40,32 @@
4140
* values.
4241
* </p>
4342
*/
44-
public class Mean implements WeightedEvaluation {
43+
public final class WeightedMean implements WeightedEvaluation {
44+
/** An instance. */
45+
private static final WeightedMean INSTANCE = new WeightedMean();
4546

46-
/** Constructs a Mean. */
47-
public Mean() {
47+
/** Create an instance. */
48+
private WeightedMean() {
4849
// Do nothing
4950
}
5051

52+
/**
53+
* Gets an instance.
54+
*
55+
* @return an instance
56+
*/
57+
public static WeightedMean getInstance() {
58+
return INSTANCE;
59+
}
60+
5161
/**
5262
* Returns the weighted arithmetic mean of the entries in the specified portion of
5363
* the input array, or <code>Double.NaN</code> if the designated subarray
5464
* is empty.
5565
* <p>
5666
* Throws <code>IllegalArgumentException</code> if either array is null.</p>
5767
* <p>
58-
* See {@link Mean} for details on the computing algorithm. The two-pass algorithm
68+
* See {@link WeightedMean} for details on the computing algorithm. The two-pass algorithm
5969
* described above is used here, with weights applied in computing both the original
6070
* estimate and the correction factor.</p>
6171
* <p>
@@ -102,7 +112,7 @@ public double evaluate(final double[] values, final double[] weights,
102112
* <p>
103113
* Throws <code>MathIllegalArgumentException</code> if either array is null.</p>
104114
* <p>
105-
* See {@link Mean} for details on the computing algorithm. The two-pass algorithm
115+
* See {@link WeightedMean} for details on the computing algorithm. The two-pass algorithm
106116
* described above is used here, with weights applied in computing both the original
107117
* estimate and the correction factor.</p>
108118
* <p>

commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/descriptive/moment/Variance.java renamed to commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/descriptive/moment/WeightedVariance.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,45 @@
2121
import org.apache.commons.math4.legacy.stat.descriptive.WeightedEvaluation;
2222

2323
/**
24-
* Computes the variance of the available values. By default, the unbiased
24+
* Computes the weighted variance of the available values. By default, the unbiased
2525
* "sample variance" definitional formula is used:
2626
* <p>
27-
* variance = sum((x_i - mean)^2) / (n - 1) </p>
27+
* variance = sum(w_i * (x_i - mean)^2) / (sum(w_i) - 1) </p>
2828
* <p>
29-
* where mean is the {@link Mean} and <code>n</code> is the number
30-
* of sample observations.</p>
29+
* where mean is the {@link WeightedMean} and <code>w_i</code> is the weight for
30+
* observation <code>x_i</code>.</p>
3131
* <p>
32-
* The "population variance" ( sum((x_i - mean)^2) / n ) can also
32+
* The "population variance" using the denominator as <code>sum(w_i)</code> can also
3333
* be computed using this statistic. The <code>isBiasCorrected</code>
3434
* property determines whether the "population" or "sample" value is
3535
* returned by the <code>evaluate</code> methods.
3636
* To compute population variances, set this property to <code>false.</code>
3737
* </p>
38-
* <p>
39-
Ø */
40-
public class Variance implements WeightedEvaluation {
38+
*/
39+
public final class WeightedVariance implements WeightedEvaluation {
4140
/**
4241
* Whether or not bias correction is applied when computing the
4342
* value of the statistic. True means that bias is corrected. See
44-
* {@link Variance} for details on the formula.
43+
* {@link WeightedVariance} for details on the formula.
4544
*/
4645
private boolean isBiasCorrected = true;
4746

4847
/**
4948
* Constructs a Variance.
5049
*/
51-
public Variance() {
50+
private WeightedVariance() {
5251
// Do nothing
5352
}
5453

54+
/**
55+
* Gets a new instance.
56+
*
57+
* @return an instance
58+
*/
59+
public static WeightedVariance getInstance() {
60+
return new WeightedVariance();
61+
}
62+
5563
/**
5664
* <p>Returns the weighted variance of the entries in the specified portion of
5765
* the input array, or <code>Double.NaN</code> if the designated subarray
@@ -105,7 +113,7 @@ public double evaluate(final double[] values, final double[] weights,
105113
if (length == 1) {
106114
var = 0.0;
107115
} else if (length > 1) {
108-
Mean mean = new Mean();
116+
WeightedMean mean = WeightedMean.getInstance();
109117
double m = mean.evaluate(values, weights, begin, length);
110118
var = evaluate(values, weights, m, begin, length);
111119
}

0 commit comments

Comments
 (0)