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