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     * @return The geometricMean, Double.NaN if no values have been added,
213     * or if the product of the available values is less than or equal to 0.
214     */
215    public double getGeometricMean() {
216        return apply(geometricMeanImpl);
217    }
218
219    /**
220     * Returns the (sample) variance of the available values.
221     *
222     * <p>This method returns the bias-corrected sample variance (using {@code n - 1} in
223     * the denominator).  Use {@link #getPopulationVariance()} for the non-bias-corrected
224     * population variance.</p>
225     *
226     * @return The variance, Double.NaN if no values have been added
227     * or 0.0 for a single value set.
228     */
229    public double getVariance() {
230        return apply(varianceImpl);
231    }
232
233    /**
234     * Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">
235     * population variance</a> of the available values.
236     *
237     * @return The population variance, Double.NaN if no values have been added,
238     * or 0.0 for a single value set.
239     */
240    public double getPopulationVariance() {
241        return apply(new Variance(false));
242    }
243
244    /**
245     * Returns the standard deviation of the available values.
246     * @return The standard deviation, Double.NaN if no values have been added
247     * or 0.0 for a single value set.
248     */
249    public double getStandardDeviation() {
250        double stdDev = Double.NaN;
251        if (getN() > 0) {
252            if (getN() > 1) {
253                stdDev = FastMath.sqrt(getVariance());
254            } else {
255                stdDev = 0.0;
256            }
257        }
258        return stdDev;
259    }
260
261    /**
262     * Returns the skewness of the available values. Skewness is a
263     * measure of the asymmetry of a given distribution.
264     * @return The skewness, Double.NaN if no values have been added
265     * or 0.0 for a value set &lt;=2.
266     */
267    public double getSkewness() {
268        return apply(skewnessImpl);
269    }
270
271    /**
272     * Returns the Kurtosis of the available values. Kurtosis is a
273     * measure of the "peakedness" of a distribution
274     * @return The kurtosis, Double.NaN if no values have been added, or 0.0
275     * for a value set &lt;=3.
276     */
277    public double getKurtosis() {
278        return apply(kurtosisImpl);
279    }
280
281    /**
282     * Returns the maximum of the available values
283     * @return The max or Double.NaN if no values have been added.
284     */
285    public double getMax() {
286        return apply(maxImpl);
287    }
288
289    /**
290    * Returns the minimum of the available values
291    * @return The min or Double.NaN if no values have been added.
292    */
293    public double getMin() {
294        return apply(minImpl);
295    }
296
297    /**
298     * Returns the number of available values
299     * @return The number of available values
300     */
301    public long getN() {
302        return eDA.getNumElements();
303    }
304
305    /**
306     * Returns the sum of the values that have been added to Univariate.
307     * @return The sum or Double.NaN if no values have been added
308     */
309    public double getSum() {
310        return apply(sumImpl);
311    }
312
313    /**
314     * Returns the sum of the squares of the available values.
315     * @return The sum of the squares or Double.NaN if no
316     * values have been added.
317     */
318    public double getSumsq() {
319        return apply(sumsqImpl);
320    }
321
322    /**
323     * Resets all statistics and storage
324     */
325    public void clear() {
326        eDA.clear();
327    }
328
329
330    /**
331     * Returns the maximum number of values that can be stored in the
332     * dataset, or INFINITE_WINDOW (-1) if there is no limit.
333     *
334     * @return The current window size or -1 if its Infinite.
335     */
336    public int getWindowSize() {
337        return windowSize;
338    }
339
340    /**
341     * WindowSize controls the number of values that contribute to the
342     * reported statistics.  For example, if windowSize is set to 3 and the
343     * values {1,2,3,4,5} have been added <strong> in that order</strong> then
344     * the <i>available values</i> are {3,4,5} and all reported statistics will
345     * be based on these values. If {@code windowSize} is decreased as a result
346     * of this call and there are more than the new value of elements in the
347     * current dataset, values from the front of the array are discarded to
348     * reduce the dataset to {@code windowSize} elements.
349     *
350     * @param windowSize sets the size of the window.
351     * @throws MathIllegalArgumentException if window size is less than 1 but
352     * not equal to {@link #INFINITE_WINDOW}
353     */
354    public void setWindowSize(int windowSize) throws MathIllegalArgumentException {
355        if (windowSize < 1 && windowSize != INFINITE_WINDOW) {
356            throw new MathIllegalArgumentException(
357                    LocalizedFormats.NOT_POSITIVE_WINDOW_SIZE, windowSize);
358        }
359
360        this.windowSize = windowSize;
361
362        // We need to check to see if we need to discard elements
363        // from the front of the array.  If the windowSize is less than
364        // the current number of elements.
365        if (windowSize != INFINITE_WINDOW && windowSize < eDA.getNumElements()) {
366            eDA.discardFrontElements(eDA.getNumElements() - windowSize);
367        }
368    }
369
370    /**
371     * Returns the current set of values in an array of double primitives.
372     * The order of addition is preserved.  The returned array is a fresh
373     * copy of the underlying data -- i.e., it is not a reference to the
374     * stored data.
375     *
376     * @return returns the current set of numbers in the order in which they
377     *         were added to this set
378     */
379    public double[] getValues() {
380        return eDA.getElements();
381    }
382
383    /**
384     * Returns the current set of values in an array of double primitives,
385     * sorted in ascending order.  The returned array is a fresh
386     * copy of the underlying data -- i.e., it is not a reference to the
387     * stored data.
388     * @return returns the current set of
389     * numbers sorted in ascending order
390     */
391    public double[] getSortedValues() {
392        double[] sort = getValues();
393        Arrays.sort(sort);
394        return sort;
395    }
396
397    /**
398     * Returns the element at the specified index
399     * @param index The Index of the element
400     * @return return the element at the specified index
401     */
402    public double getElement(int index) {
403        return eDA.getElement(index);
404    }
405
406    /**
407     * Returns an estimate for the pth percentile of the stored values.
408     * <p>
409     * The implementation provided here follows the first estimation procedure presented
410     * <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm">here.</a>
411     * </p><p>
412     * <strong>Preconditions</strong>:<ul>
413     * <li><code>0 &lt; p &le; 100</code> (otherwise an
414     * <code>MathIllegalArgumentException</code> is thrown)</li>
415     * <li>at least one value must be stored (returns <code>Double.NaN
416     *     </code> otherwise)</li>
417     * </ul></p>
418     *
419     * @param p the requested percentile (scaled from 0 - 100)
420     * @return An estimate for the pth percentile of the stored data
421     * @throws MathIllegalStateException if percentile implementation has been
422     *  overridden and the supplied implementation does not support setQuantile
423     * @throws MathIllegalArgumentException if p is not a valid quantile
424     */
425    public double getPercentile(double p) throws MathIllegalStateException, MathIllegalArgumentException {
426        if (percentileImpl instanceof Percentile) {
427            ((Percentile) percentileImpl).setQuantile(p);
428        } else {
429            try {
430                percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
431                        new Class[] {Double.TYPE}).invoke(percentileImpl,
432                                new Object[] {Double.valueOf(p)});
433            } catch (NoSuchMethodException e1) { // Setter guard should prevent
434                throw new MathIllegalStateException(
435                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
436                      percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
437            } catch (IllegalAccessException e2) {
438                throw new MathIllegalStateException(
439                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
440                      SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
441            } catch (InvocationTargetException e3) {
442                throw new IllegalStateException(e3.getCause());
443            }
444        }
445        return apply(percentileImpl);
446    }
447
448    /**
449     * Generates a text report displaying univariate statistics from values
450     * that have been added.  Each statistic is displayed on a separate
451     * line.
452     *
453     * @return String with line feeds displaying statistics
454     */
455    @Override
456    public String toString() {
457        StringBuilder outBuffer = new StringBuilder();
458        String endl = "\n";
459        outBuffer.append("DescriptiveStatistics:").append(endl);
460        outBuffer.append("n: ").append(getN()).append(endl);
461        outBuffer.append("min: ").append(getMin()).append(endl);
462        outBuffer.append("max: ").append(getMax()).append(endl);
463        outBuffer.append("mean: ").append(getMean()).append(endl);
464        outBuffer.append("std dev: ").append(getStandardDeviation())
465            .append(endl);
466        try {
467            // No catch for MIAE because actual parameter is valid below
468            outBuffer.append("median: ").append(getPercentile(50)).append(endl);
469        } catch (MathIllegalStateException ex) {
470            outBuffer.append("median: unavailable").append(endl);
471        }
472        outBuffer.append("skewness: ").append(getSkewness()).append(endl);
473        outBuffer.append("kurtosis: ").append(getKurtosis()).append(endl);
474        return outBuffer.toString();
475    }
476
477    /**
478     * Apply the given statistic to the data associated with this set of statistics.
479     * @param stat the statistic to apply
480     * @return the computed value of the statistic.
481     */
482    public double apply(UnivariateStatistic stat) {
483        // No try-catch or advertised exception here because arguments are guaranteed valid
484        return eDA.compute(stat);
485    }
486
487    // Implementation getters and setter
488
489    /**
490     * Returns the currently configured mean implementation.
491     *
492     * @return the UnivariateStatistic implementing the mean
493     * @since 1.2
494     */
495    public synchronized UnivariateStatistic getMeanImpl() {
496        return meanImpl;
497    }
498
499    /**
500     * <p>Sets the implementation for the mean.</p>
501     *
502     * @param meanImpl the UnivariateStatistic instance to use
503     * for computing the mean
504     * @since 1.2
505     */
506    public synchronized void setMeanImpl(UnivariateStatistic meanImpl) {
507        this.meanImpl = meanImpl;
508    }
509
510    /**
511     * Returns the currently configured geometric mean implementation.
512     *
513     * @return the UnivariateStatistic implementing the geometric mean
514     * @since 1.2
515     */
516    public synchronized UnivariateStatistic getGeometricMeanImpl() {
517        return geometricMeanImpl;
518    }
519
520    /**
521     * <p>Sets the implementation for the gemoetric mean.</p>
522     *
523     * @param geometricMeanImpl the UnivariateStatistic instance to use
524     * for computing the geometric mean
525     * @since 1.2
526     */
527    public synchronized void setGeometricMeanImpl(
528            UnivariateStatistic geometricMeanImpl) {
529        this.geometricMeanImpl = geometricMeanImpl;
530    }
531
532    /**
533     * Returns the currently configured kurtosis implementation.
534     *
535     * @return the UnivariateStatistic implementing the kurtosis
536     * @since 1.2
537     */
538    public synchronized UnivariateStatistic getKurtosisImpl() {
539        return kurtosisImpl;
540    }
541
542    /**
543     * <p>Sets the implementation for the kurtosis.</p>
544     *
545     * @param kurtosisImpl the UnivariateStatistic instance to use
546     * for computing the kurtosis
547     * @since 1.2
548     */
549    public synchronized void setKurtosisImpl(UnivariateStatistic kurtosisImpl) {
550        this.kurtosisImpl = kurtosisImpl;
551    }
552
553    /**
554     * Returns the currently configured maximum implementation.
555     *
556     * @return the UnivariateStatistic implementing the maximum
557     * @since 1.2
558     */
559    public synchronized UnivariateStatistic getMaxImpl() {
560        return maxImpl;
561    }
562
563    /**
564     * <p>Sets the implementation for the maximum.</p>
565     *
566     * @param maxImpl the UnivariateStatistic instance to use
567     * for computing the maximum
568     * @since 1.2
569     */
570    public synchronized void setMaxImpl(UnivariateStatistic maxImpl) {
571        this.maxImpl = maxImpl;
572    }
573
574    /**
575     * Returns the currently configured minimum implementation.
576     *
577     * @return the UnivariateStatistic implementing the minimum
578     * @since 1.2
579     */
580    public synchronized UnivariateStatistic getMinImpl() {
581        return minImpl;
582    }
583
584    /**
585     * <p>Sets the implementation for the minimum.</p>
586     *
587     * @param minImpl the UnivariateStatistic instance to use
588     * for computing the minimum
589     * @since 1.2
590     */
591    public synchronized void setMinImpl(UnivariateStatistic minImpl) {
592        this.minImpl = minImpl;
593    }
594
595    /**
596     * Returns the currently configured percentile implementation.
597     *
598     * @return the UnivariateStatistic implementing the percentile
599     * @since 1.2
600     */
601    public synchronized UnivariateStatistic getPercentileImpl() {
602        return percentileImpl;
603    }
604
605    /**
606     * Sets the implementation to be used by {@link #getPercentile(double)}.
607     * The supplied <code>UnivariateStatistic</code> must provide a
608     * <code>setQuantile(double)</code> method; otherwise
609     * <code>IllegalArgumentException</code> is thrown.
610     *
611     * @param percentileImpl the percentileImpl to set
612     * @throws MathIllegalArgumentException if the supplied implementation does not
613     *  provide a <code>setQuantile</code> method
614     * @since 1.2
615     */
616    public synchronized void setPercentileImpl(UnivariateStatistic percentileImpl)
617    throws MathIllegalArgumentException {
618        try {
619            percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
620                    new Class[] {Double.TYPE}).invoke(percentileImpl,
621                            new Object[] {Double.valueOf(50.0d)});
622        } catch (NoSuchMethodException e1) {
623            throw new MathIllegalArgumentException(
624                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
625                  percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
626        } catch (IllegalAccessException e2) {
627            throw new MathIllegalArgumentException(
628                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
629                  SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
630        } catch (InvocationTargetException e3) {
631            throw new IllegalArgumentException(e3.getCause());
632        }
633        this.percentileImpl = percentileImpl;
634    }
635
636    /**
637     * Returns the currently configured skewness implementation.
638     *
639     * @return the UnivariateStatistic implementing the skewness
640     * @since 1.2
641     */
642    public synchronized UnivariateStatistic getSkewnessImpl() {
643        return skewnessImpl;
644    }
645
646    /**
647     * <p>Sets the implementation for the skewness.</p>
648     *
649     * @param skewnessImpl the UnivariateStatistic instance to use
650     * for computing the skewness
651     * @since 1.2
652     */
653    public synchronized void setSkewnessImpl(
654            UnivariateStatistic skewnessImpl) {
655        this.skewnessImpl = skewnessImpl;
656    }
657
658    /**
659     * Returns the currently configured variance implementation.
660     *
661     * @return the UnivariateStatistic implementing the variance
662     * @since 1.2
663     */
664    public synchronized UnivariateStatistic getVarianceImpl() {
665        return varianceImpl;
666    }
667
668    /**
669     * <p>Sets the implementation for the variance.</p>
670     *
671     * @param varianceImpl the UnivariateStatistic instance to use
672     * for computing the variance
673     * @since 1.2
674     */
675    public synchronized void setVarianceImpl(
676            UnivariateStatistic varianceImpl) {
677        this.varianceImpl = varianceImpl;
678    }
679
680    /**
681     * Returns the currently configured sum of squares implementation.
682     *
683     * @return the UnivariateStatistic implementing the sum of squares
684     * @since 1.2
685     */
686    public synchronized UnivariateStatistic getSumsqImpl() {
687        return sumsqImpl;
688    }
689
690    /**
691     * <p>Sets the implementation for the sum of squares.</p>
692     *
693     * @param sumsqImpl the UnivariateStatistic instance to use
694     * for computing the sum of squares
695     * @since 1.2
696     */
697    public synchronized void setSumsqImpl(UnivariateStatistic sumsqImpl) {
698        this.sumsqImpl = sumsqImpl;
699    }
700
701    /**
702     * Returns the currently configured sum implementation.
703     *
704     * @return the UnivariateStatistic implementing the sum
705     * @since 1.2
706     */
707    public synchronized UnivariateStatistic getSumImpl() {
708        return sumImpl;
709    }
710
711    /**
712     * <p>Sets the implementation for the sum.</p>
713     *
714     * @param sumImpl the UnivariateStatistic instance to use
715     * for computing the sum
716     * @since 1.2
717     */
718    public synchronized void setSumImpl(UnivariateStatistic sumImpl) {
719        this.sumImpl = sumImpl;
720    }
721
722    /**
723     * Returns a copy of this DescriptiveStatistics instance with the same internal state.
724     *
725     * @return a copy of this
726     */
727    public DescriptiveStatistics copy() {
728        DescriptiveStatistics result = new DescriptiveStatistics();
729        // No try-catch or advertised exception because parms are guaranteed valid
730        copy(this, result);
731        return result;
732    }
733
734    /**
735     * Copies source to dest.
736     * <p>Neither source nor dest can be null.</p>
737     *
738     * @param source DescriptiveStatistics to copy
739     * @param dest DescriptiveStatistics to copy to
740     * @throws NullArgumentException if either source or dest is null
741     */
742    public static void copy(DescriptiveStatistics source, DescriptiveStatistics dest)
743        throws NullArgumentException {
744        MathUtils.checkNotNull(source);
745        MathUtils.checkNotNull(dest);
746        // Copy data and window size
747        dest.eDA = source.eDA.copy();
748        dest.windowSize = source.windowSize;
749
750        // Copy implementations
751        dest.maxImpl = source.maxImpl.copy();
752        dest.meanImpl = source.meanImpl.copy();
753        dest.minImpl = source.minImpl.copy();
754        dest.sumImpl = source.sumImpl.copy();
755        dest.varianceImpl = source.varianceImpl.copy();
756        dest.sumsqImpl = source.sumsqImpl.copy();
757        dest.geometricMeanImpl = source.geometricMeanImpl.copy();
758        dest.kurtosisImpl = source.kurtosisImpl;
759        dest.skewnessImpl = source.skewnessImpl;
760        dest.percentileImpl = source.percentileImpl;
761    }
762}