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 */
017package org.apache.commons.math4.legacy.stat.descriptive;
018
019import java.lang.reflect.InvocationTargetException;
020import java.util.Arrays;
021
022import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
023import org.apache.commons.math4.legacy.exception.MathIllegalStateException;
024import org.apache.commons.math4.legacy.exception.NullArgumentException;
025import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
026import org.apache.commons.math4.legacy.stat.descriptive.moment.GeometricMean;
027import org.apache.commons.math4.legacy.stat.descriptive.moment.Kurtosis;
028import org.apache.commons.math4.legacy.stat.descriptive.moment.Mean;
029import org.apache.commons.math4.legacy.stat.descriptive.moment.Skewness;
030import org.apache.commons.math4.legacy.stat.descriptive.moment.Variance;
031import org.apache.commons.math4.legacy.stat.descriptive.rank.Max;
032import org.apache.commons.math4.legacy.stat.descriptive.rank.Min;
033import org.apache.commons.math4.legacy.stat.descriptive.rank.Percentile;
034import org.apache.commons.math4.legacy.stat.descriptive.summary.Sum;
035import org.apache.commons.math4.legacy.stat.descriptive.summary.SumOfSquares;
036import org.apache.commons.math4.core.jdkmath.JdkMath;
037
038
039/**
040 * Maintains a dataset of values of a single variable and computes descriptive
041 * statistics based on stored data.
042 * <p>
043 * The {@link #getWindowSize() windowSize}
044 * property sets a limit on the number of values that can be stored in the
045 * dataset. The default value, INFINITE_WINDOW, puts no limit on the size of
046 * the dataset. This value should be used with caution, as the backing store
047 * will grow without bound in this case.  For very large datasets,
048 * {@link SummaryStatistics}, which does not store the dataset, should be used
049 * instead of this class. If <code>windowSize</code> is not INFINITE_WINDOW and
050 * more values are added than can be stored in the dataset, new values are
051 * added in a "rolling" manner, with new values replacing the "oldest" values
052 * in the dataset.
053 * <p>
054 * Note: this class is not threadsafe.  Use
055 * {@link SynchronizedDescriptiveStatistics} if concurrent access from multiple
056 * threads is required.
057 */
058public class DescriptiveStatistics implements StatisticalSummary {
059
060    /**
061     * Represents an infinite window size.  When the {@link #getWindowSize()}
062     * returns this value, there is no limit to the number of data values
063     * that can be stored in the dataset.
064     */
065    public static final int INFINITE_WINDOW = -1;
066
067    /** Name of the setQuantile method. */
068    private static final String SET_QUANTILE_METHOD_NAME = "setQuantile";
069
070    /** hold the window size. */
071    private int windowSize = INFINITE_WINDOW;
072
073    /** Stored data values. */
074    private ResizableDoubleArray eDA = new ResizableDoubleArray();
075
076    /** Mean statistic implementation - can be reset by setter. */
077    private UnivariateStatistic meanImpl = new Mean();
078
079    /** Geometric mean statistic implementation - can be reset by setter. */
080    private UnivariateStatistic geometricMeanImpl = new GeometricMean();
081
082    /** Kurtosis statistic implementation - can be reset by setter. */
083    private UnivariateStatistic kurtosisImpl = new Kurtosis();
084
085    /** Maximum statistic implementation - can be reset by setter. */
086    private UnivariateStatistic maxImpl = new Max();
087
088    /** Minimum statistic implementation - can be reset by setter. */
089    private UnivariateStatistic minImpl = new Min();
090
091    /** Percentile statistic implementation - can be reset by setter. */
092    private UnivariateStatistic percentileImpl = new Percentile();
093
094    /** Skewness statistic implementation - can be reset by setter. */
095    private UnivariateStatistic skewnessImpl = new Skewness();
096
097    /** Variance statistic implementation - can be reset by setter. */
098    private UnivariateStatistic varianceImpl = new Variance();
099
100    /** Sum of squares statistic implementation - can be reset by setter. */
101    private UnivariateStatistic sumsqImpl = new SumOfSquares();
102
103    /** Sum statistic implementation - can be reset by setter. */
104    private UnivariateStatistic sumImpl = new Sum();
105
106    /**
107     * Construct a {@code DescriptiveStatistics} instance with an infinite
108     * window.
109     */
110    public DescriptiveStatistics() {
111    }
112
113    /**
114     * Construct a {@code DescriptiveStatistics} instance with the specified
115     * window.
116     *
117     * @param window the window size.
118     * @throws MathIllegalArgumentException if window size is less than 1 but
119     * not equal to {@link #INFINITE_WINDOW}
120     */
121    public DescriptiveStatistics(int window) throws MathIllegalArgumentException {
122        setWindowSize(window);
123    }
124
125    /**
126     * Construct a {@code DescriptiveStatistics} instance with an infinite
127     * window and the initial data values in {@code initialDoubleArray}.
128     * If {@code initialDoubleArray} is {@code null}, then this constructor
129     * corresponds to the {@link #DescriptiveStatistics() default constructor}.
130     *
131     * @param initialDoubleArray the initial double[].
132     */
133    public DescriptiveStatistics(double[] initialDoubleArray) {
134        if (initialDoubleArray != null) {
135            eDA = new ResizableDoubleArray(initialDoubleArray);
136        }
137    }
138
139    /**
140     * Construct a DescriptiveStatistics instance with an infinite window
141     * and the initial data values in {@code initialDoubleArray}.
142     * If {@code initialDoubleArray} is {@code null}, then this constructor
143     * corresponds to {@link #DescriptiveStatistics() }.
144     *
145     * @param initialDoubleArray the initial Double[].
146     */
147    public DescriptiveStatistics(Double[] initialDoubleArray) {
148        if (initialDoubleArray != null) {
149            eDA = new ResizableDoubleArray(initialDoubleArray.length);
150            for(double initialValue : initialDoubleArray) {
151                eDA.addElement(initialValue);
152            }
153        }
154    }
155
156    /**
157     * Copy constructor. Construct a new {@code DescriptiveStatistics} instance
158     * that is a copy of {@code original}.
159     *
160     * @param original DescriptiveStatistics instance to copy
161     * @throws NullArgumentException if original is null
162     */
163    public DescriptiveStatistics(DescriptiveStatistics original) throws NullArgumentException {
164        copy(original, this);
165    }
166
167    /**
168     * Adds the value to the dataset. If the dataset is at the maximum size
169     * (i.e., the number of stored elements equals the currently configured
170     * windowSize), the first (oldest) element in the dataset is discarded
171     * to make room for the new value.
172     *
173     * @param v the value to be added
174     */
175    public void addValue(double v) {
176        if (windowSize != INFINITE_WINDOW) {
177            if (getN() == windowSize) {
178                eDA.addElementRolling(v);
179            } else if (getN() < windowSize) {
180                eDA.addElement(v);
181            }
182        } else {
183            eDA.addElement(v);
184        }
185    }
186
187    /**
188     * Removes the most recent value from the dataset.
189     *
190     * @throws MathIllegalStateException if there are no elements stored
191     */
192    public void removeMostRecentValue() throws MathIllegalStateException {
193        try {
194            eDA.discardMostRecentElements(1);
195        } catch (MathIllegalArgumentException ex) {
196            throw new MathIllegalStateException(LocalizedFormats.NO_DATA);
197        }
198    }
199
200    /**
201     * Replaces the most recently stored value with the given value.
202     * There must be at least one element stored to call this method.
203     *
204     * @param v the value to replace the most recent stored value
205     * @return replaced value
206     * @throws MathIllegalStateException if there are no elements stored
207     */
208    public double replaceMostRecentValue(double v) throws MathIllegalStateException {
209        return eDA.substituteMostRecentElement(v);
210    }
211
212    /**
213     * Returns the <a href="http://www.xycoon.com/arithmetic_mean.htm">
214     * arithmetic mean </a> of the available values.
215     * @return The mean or Double.NaN if no values have been added.
216     */
217    @Override
218    public double getMean() {
219        return apply(meanImpl);
220    }
221
222    /**
223     * Returns the <a href="http://www.xycoon.com/geometric_mean.htm">
224     * geometric mean </a> of the available values.
225     * <p>
226     * See {@link GeometricMean} for details on the computing algorithm.</p>
227     *
228     * @return The geometricMean, Double.NaN if no values have been added,
229     * or if any negative values have been added.
230     */
231    public double getGeometricMean() {
232        return apply(geometricMeanImpl);
233    }
234
235    /**
236     * Returns the (sample) variance of the available values.
237     *
238     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in
239     * the denominator).  Use {@link #getPopulationVariance()} for the non-bias-corrected
240     * population variance.</p>
241     *
242     * @return The variance, Double.NaN if no values have been added
243     * or 0.0 for a single value set.
244     */
245    @Override
246    public double getVariance() {
247        return apply(varianceImpl);
248    }
249
250    /**
251     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">
252     * population variance</a> of the available values.
253     *
254     * @return The population variance, Double.NaN if no values have been added,
255     * or 0.0 for a single value set.
256     */
257    public double getPopulationVariance() {
258        return apply(new Variance(false));
259    }
260
261    /**
262     * Returns the standard deviation of the available values.
263     * @return The standard deviation, Double.NaN if no values have been added
264     * or 0.0 for a single value set.
265     */
266    @Override
267    public double getStandardDeviation() {
268        double stdDev = Double.NaN;
269        if (getN() > 0) {
270            if (getN() > 1) {
271                stdDev = JdkMath.sqrt(getVariance());
272            } else {
273                stdDev = 0.0;
274            }
275        }
276        return stdDev;
277    }
278
279    /**
280     * Returns the quadratic mean, a.k.a.
281     * <a href="http://mathworld.wolfram.com/Root-Mean-Square.html">
282     * root-mean-square</a> of the available values
283     * @return The quadratic mean or {@code Double.NaN} if no values
284     * have been added.
285     */
286    public double getQuadraticMean() {
287        final long n = getN();
288        return n > 0 ? JdkMath.sqrt(getSumsq() / n) : Double.NaN;
289    }
290
291    /**
292     * Returns the skewness of the available values. Skewness is a
293     * measure of the asymmetry of a given distribution.
294     *
295     * @return The skewness, Double.NaN if less than 3 values have been added.
296     */
297    public double getSkewness() {
298        return apply(skewnessImpl);
299    }
300
301    /**
302     * Returns the Kurtosis of the available values. Kurtosis is a
303     * measure of the "peakedness" of a distribution.
304     *
305     * @return The kurtosis, Double.NaN if less than 4 values have been added.
306     */
307    public double getKurtosis() {
308        return apply(kurtosisImpl);
309    }
310
311    /**
312     * Returns the maximum of the available values.
313     * @return The max or Double.NaN if no values have been added.
314     */
315    @Override
316    public double getMax() {
317        return apply(maxImpl);
318    }
319
320    /**
321    * Returns the minimum of the available values.
322    * @return The min or Double.NaN if no values have been added.
323    */
324    @Override
325    public double getMin() {
326        return apply(minImpl);
327    }
328
329    /**
330     * Returns the number of available values.
331     * @return The number of available values
332     */
333    @Override
334    public long getN() {
335        return eDA.getNumElements();
336    }
337
338    /**
339     * Returns the sum of the values that have been added to Univariate.
340     * @return The sum or Double.NaN if no values have been added
341     */
342    @Override
343    public double getSum() {
344        return apply(sumImpl);
345    }
346
347    /**
348     * Returns the sum of the squares of the available values.
349     * @return The sum of the squares or Double.NaN if no
350     * values have been added.
351     */
352    public double getSumsq() {
353        return apply(sumsqImpl);
354    }
355
356    /**
357     * Resets all statistics and storage.
358     */
359    public void clear() {
360        eDA.clear();
361    }
362
363
364    /**
365     * Returns the maximum number of values that can be stored in the
366     * dataset, or INFINITE_WINDOW (-1) if there is no limit.
367     *
368     * @return The current window size or -1 if its Infinite.
369     */
370    public int getWindowSize() {
371        return windowSize;
372    }
373
374    /**
375     * WindowSize controls the number of values that contribute to the
376     * reported statistics.  For example, if windowSize is set to 3 and the
377     * values {1,2,3,4,5} have been added <strong> in that order</strong> then
378     * the <i>available values</i> are {3,4,5} and all reported statistics will
379     * be based on these values. If {@code windowSize} is decreased as a result
380     * of this call and there are more than the new value of elements in the
381     * current dataset, values from the front of the array are discarded to
382     * reduce the dataset to {@code windowSize} elements.
383     *
384     * @param windowSize sets the size of the window.
385     * @throws MathIllegalArgumentException if window size is less than 1 but
386     * not equal to {@link #INFINITE_WINDOW}
387     */
388    public void setWindowSize(int windowSize) throws MathIllegalArgumentException {
389        if (windowSize < 1 && windowSize != INFINITE_WINDOW) {
390            throw new MathIllegalArgumentException(
391                    LocalizedFormats.NOT_POSITIVE_WINDOW_SIZE, windowSize);
392        }
393
394        this.windowSize = windowSize;
395
396        // We need to check to see if we need to discard elements
397        // from the front of the array.  If the windowSize is less than
398        // the current number of elements.
399        if (windowSize != INFINITE_WINDOW && windowSize < eDA.getNumElements()) {
400            eDA.discardFrontElements(eDA.getNumElements() - windowSize);
401        }
402    }
403
404    /**
405     * Returns the current set of values in an array of double primitives.
406     * The order of addition is preserved.  The returned array is a fresh
407     * copy of the underlying data -- i.e., it is not a reference to the
408     * stored data.
409     *
410     * @return returns the current set of numbers in the order in which they
411     *         were added to this set
412     */
413    public double[] getValues() {
414        return eDA.getElements();
415    }
416
417    /**
418     * Returns the current set of values in an array of double primitives,
419     * sorted in ascending order.  The returned array is a fresh
420     * copy of the underlying data -- i.e., it is not a reference to the
421     * stored data.
422     * @return returns the current set of
423     * numbers sorted in ascending order
424     */
425    public double[] getSortedValues() {
426        double[] sort = getValues();
427        Arrays.sort(sort);
428        return sort;
429    }
430
431    /**
432     * Returns the element at the specified index.
433     * @param index The Index of the element
434     * @return return the element at the specified index
435     */
436    public double getElement(int index) {
437        return eDA.getElement(index);
438    }
439
440    /**
441     * Returns an estimate for the pth percentile of the stored values.
442     * <p>
443     * The implementation provided here follows the first estimation procedure presented
444     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm">here.</a>
445     * </p><p>
446     * <strong>Preconditions</strong>:<ul>
447     * <li><code>0 &lt; p &le; 100</code> (otherwise an
448     * <code>MathIllegalArgumentException</code> is thrown)</li>
449     * <li>at least one value must be stored (returns <code>Double.NaN
450     *     </code> otherwise)</li>
451     * </ul>
452     *
453     * @param p the requested percentile (scaled from 0 - 100)
454     * @return An estimate for the pth percentile of the stored data
455     * @throws MathIllegalStateException if percentile implementation has been
456     *  overridden and the supplied implementation does not support setQuantile
457     * @throws MathIllegalArgumentException if p is not a valid quantile
458     */
459    public double getPercentile(double p) throws MathIllegalStateException, MathIllegalArgumentException {
460        if (percentileImpl instanceof Percentile) {
461            ((Percentile) percentileImpl).setQuantile(p);
462        } else {
463            try {
464                percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
465                        new Class[] {Double.TYPE}).invoke(percentileImpl,
466                                new Object[] {Double.valueOf(p)});
467            } catch (NoSuchMethodException e1) { // Setter guard should prevent
468                throw new MathIllegalStateException(
469                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
470                      percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
471            } catch (IllegalAccessException e2) {
472                throw new MathIllegalStateException(
473                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
474                      SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
475            } catch (InvocationTargetException e3) {
476                throw new IllegalStateException(e3.getCause());
477            }
478        }
479        return apply(percentileImpl);
480    }
481
482    /**
483     * Generates a text report displaying univariate statistics from values
484     * that have been added.  Each statistic is displayed on a separate
485     * line.
486     *
487     * @return String with line feeds displaying statistics
488     */
489    @Override
490    public String toString() {
491        StringBuilder outBuffer = new StringBuilder();
492        String endl = "\n";
493        outBuffer.append("DescriptiveStatistics:").append(endl);
494        outBuffer.append("n: ").append(getN()).append(endl);
495        outBuffer.append("min: ").append(getMin()).append(endl);
496        outBuffer.append("max: ").append(getMax()).append(endl);
497        outBuffer.append("mean: ").append(getMean()).append(endl);
498        outBuffer.append("std dev: ").append(getStandardDeviation())
499            .append(endl);
500        try {
501            // No catch for MIAE because actual parameter is valid below
502            outBuffer.append("median: ").append(getPercentile(50)).append(endl);
503        } catch (MathIllegalStateException ex) {
504            outBuffer.append("median: unavailable").append(endl);
505        }
506        outBuffer.append("skewness: ").append(getSkewness()).append(endl);
507        outBuffer.append("kurtosis: ").append(getKurtosis()).append(endl);
508        return outBuffer.toString();
509    }
510
511    /**
512     * Apply the given statistic to the data associated with this set of statistics.
513     * @param stat the statistic to apply
514     * @return the computed value of the statistic.
515     */
516    public double apply(UnivariateStatistic stat) {
517        // No try-catch or advertised exception here because arguments are guaranteed valid
518        return eDA.compute(stat);
519    }
520
521    // Implementation getters and setter
522
523    /**
524     * Returns the currently configured mean implementation.
525     *
526     * @return the UnivariateStatistic implementing the mean
527     * @since 1.2
528     */
529    public synchronized UnivariateStatistic getMeanImpl() {
530        return meanImpl;
531    }
532
533    /**
534     * <p>Sets the implementation for the mean.</p>
535     *
536     * @param meanImpl the UnivariateStatistic instance to use
537     * for computing the mean
538     * @since 1.2
539     */
540    public synchronized void setMeanImpl(UnivariateStatistic meanImpl) {
541        this.meanImpl = meanImpl;
542    }
543
544    /**
545     * Returns the currently configured geometric mean implementation.
546     *
547     * @return the UnivariateStatistic implementing the geometric mean
548     * @since 1.2
549     */
550    public synchronized UnivariateStatistic getGeometricMeanImpl() {
551        return geometricMeanImpl;
552    }
553
554    /**
555     * Sets the implementation for the geometric mean.
556     *
557     * @param geometricMeanImpl the UnivariateStatistic instance to use
558     * for computing the geometric mean
559     * @since 1.2
560     */
561    public synchronized void setGeometricMeanImpl(
562            UnivariateStatistic geometricMeanImpl) {
563        this.geometricMeanImpl = geometricMeanImpl;
564    }
565
566    /**
567     * Returns the currently configured kurtosis implementation.
568     *
569     * @return the UnivariateStatistic implementing the kurtosis
570     * @since 1.2
571     */
572    public synchronized UnivariateStatistic getKurtosisImpl() {
573        return kurtosisImpl;
574    }
575
576    /**
577     * Sets the implementation for the kurtosis.
578     *
579     * @param kurtosisImpl the UnivariateStatistic instance to use
580     * for computing the kurtosis
581     * @since 1.2
582     */
583    public synchronized void setKurtosisImpl(UnivariateStatistic kurtosisImpl) {
584        this.kurtosisImpl = kurtosisImpl;
585    }
586
587    /**
588     * Returns the currently configured maximum implementation.
589     *
590     * @return the UnivariateStatistic implementing the maximum
591     * @since 1.2
592     */
593    public synchronized UnivariateStatistic getMaxImpl() {
594        return maxImpl;
595    }
596
597    /**
598     * Sets the implementation for the maximum.
599     *
600     * @param maxImpl the UnivariateStatistic instance to use
601     * for computing the maximum
602     * @since 1.2
603     */
604    public synchronized void setMaxImpl(UnivariateStatistic maxImpl) {
605        this.maxImpl = maxImpl;
606    }
607
608    /**
609     * Returns the currently configured minimum implementation.
610     *
611     * @return the UnivariateStatistic implementing the minimum
612     * @since 1.2
613     */
614    public synchronized UnivariateStatistic getMinImpl() {
615        return minImpl;
616    }
617
618    /**
619     * Sets the implementation for the minimum.
620     *
621     * @param minImpl the UnivariateStatistic instance to use
622     * for computing the minimum
623     * @since 1.2
624     */
625    public synchronized void setMinImpl(UnivariateStatistic minImpl) {
626        this.minImpl = minImpl;
627    }
628
629    /**
630     * Returns the currently configured percentile implementation.
631     *
632     * @return the UnivariateStatistic implementing the percentile
633     * @since 1.2
634     */
635    public synchronized UnivariateStatistic getPercentileImpl() {
636        return percentileImpl;
637    }
638
639    /**
640     * Sets the implementation to be used by {@link #getPercentile(double)}.
641     * The supplied <code>UnivariateStatistic</code> must provide a
642     * <code>setQuantile(double)</code> method; otherwise
643     * <code>IllegalArgumentException</code> is thrown.
644     *
645     * @param percentileImpl the percentileImpl to set
646     * @throws MathIllegalArgumentException if the supplied implementation does not
647     *  provide a <code>setQuantile</code> method
648     * @since 1.2
649     */
650    public synchronized void setPercentileImpl(UnivariateStatistic percentileImpl)
651    throws MathIllegalArgumentException {
652        try {
653            percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
654                    new Class[] {Double.TYPE}).invoke(percentileImpl,
655                            new Object[] {Double.valueOf(50.0d)});
656        } catch (NoSuchMethodException e1) {
657            throw new MathIllegalArgumentException(
658                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
659                  percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
660        } catch (IllegalAccessException e2) {
661            throw new MathIllegalArgumentException(
662                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
663                  SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
664        } catch (InvocationTargetException e3) {
665            throw new IllegalArgumentException(e3.getCause());
666        }
667        this.percentileImpl = percentileImpl;
668    }
669
670    /**
671     * Returns the currently configured skewness implementation.
672     *
673     * @return the UnivariateStatistic implementing the skewness
674     * @since 1.2
675     */
676    public synchronized UnivariateStatistic getSkewnessImpl() {
677        return skewnessImpl;
678    }
679
680    /**
681     * Sets the implementation for the skewness.
682     *
683     * @param skewnessImpl the UnivariateStatistic instance to use
684     * for computing the skewness
685     * @since 1.2
686     */
687    public synchronized void setSkewnessImpl(
688            UnivariateStatistic skewnessImpl) {
689        this.skewnessImpl = skewnessImpl;
690    }
691
692    /**
693     * Returns the currently configured variance implementation.
694     *
695     * @return the UnivariateStatistic implementing the variance
696     * @since 1.2
697     */
698    public synchronized UnivariateStatistic getVarianceImpl() {
699        return varianceImpl;
700    }
701
702    /**
703     * Sets the implementation for the variance.
704     *
705     * @param varianceImpl the UnivariateStatistic instance to use
706     * for computing the variance
707     * @since 1.2
708     */
709    public synchronized void setVarianceImpl(
710            UnivariateStatistic varianceImpl) {
711        this.varianceImpl = varianceImpl;
712    }
713
714    /**
715     * Returns the currently configured sum of squares implementation.
716     *
717     * @return the UnivariateStatistic implementing the sum of squares
718     * @since 1.2
719     */
720    public synchronized UnivariateStatistic getSumsqImpl() {
721        return sumsqImpl;
722    }
723
724    /**
725     * Sets the implementation for the sum of squares.
726     *
727     * @param sumsqImpl the UnivariateStatistic instance to use
728     * for computing the sum of squares
729     * @since 1.2
730     */
731    public synchronized void setSumsqImpl(UnivariateStatistic sumsqImpl) {
732        this.sumsqImpl = sumsqImpl;
733    }
734
735    /**
736     * Returns the currently configured sum implementation.
737     *
738     * @return the UnivariateStatistic implementing the sum
739     * @since 1.2
740     */
741    public synchronized UnivariateStatistic getSumImpl() {
742        return sumImpl;
743    }
744
745    /**
746     * Sets the implementation for the sum.
747     *
748     * @param sumImpl the UnivariateStatistic instance to use
749     * for computing the sum
750     * @since 1.2
751     */
752    public synchronized void setSumImpl(UnivariateStatistic sumImpl) {
753        this.sumImpl = sumImpl;
754    }
755
756    /**
757     * Returns a copy of this DescriptiveStatistics instance with the same internal state.
758     *
759     * @return a copy of this
760     */
761    public DescriptiveStatistics copy() {
762        DescriptiveStatistics result = new DescriptiveStatistics();
763        // No try-catch or advertised exception because parms are guaranteed valid
764        copy(this, result);
765        return result;
766    }
767
768    /**
769     * Copies source to dest.
770     * <p>Neither source nor dest can be null.</p>
771     *
772     * @param source DescriptiveStatistics to copy
773     * @param dest DescriptiveStatistics to copy to
774     * @throws NullArgumentException if either source or dest is null
775     */
776    public static void copy(DescriptiveStatistics source, DescriptiveStatistics dest)
777        throws NullArgumentException {
778        NullArgumentException.check(source);
779        NullArgumentException.check(dest);
780        // Copy data and window size
781        dest.eDA = source.eDA.copy();
782        dest.windowSize = source.windowSize;
783
784        // Copy implementations
785        dest.maxImpl = source.maxImpl.copy();
786        dest.meanImpl = source.meanImpl.copy();
787        dest.minImpl = source.minImpl.copy();
788        dest.sumImpl = source.sumImpl.copy();
789        dest.varianceImpl = source.varianceImpl.copy();
790        dest.sumsqImpl = source.sumsqImpl.copy();
791        dest.geometricMeanImpl = source.geometricMeanImpl.copy();
792        dest.kurtosisImpl = source.kurtosisImpl;
793        dest.skewnessImpl = source.skewnessImpl;
794        dest.percentileImpl = source.percentileImpl;
795    }
796}