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