View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math3.stat.descriptive;
18  
19  import java.io.Serializable;
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.Arrays;
22  
23  import org.apache.commons.math3.exception.MathIllegalArgumentException;
24  import org.apache.commons.math3.exception.NullArgumentException;
25  import org.apache.commons.math3.exception.MathIllegalStateException;
26  import org.apache.commons.math3.exception.util.LocalizedFormats;
27  import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
28  import org.apache.commons.math3.stat.descriptive.moment.Kurtosis;
29  import org.apache.commons.math3.stat.descriptive.moment.Mean;
30  import org.apache.commons.math3.stat.descriptive.moment.Skewness;
31  import org.apache.commons.math3.stat.descriptive.moment.Variance;
32  import org.apache.commons.math3.stat.descriptive.rank.Max;
33  import org.apache.commons.math3.stat.descriptive.rank.Min;
34  import org.apache.commons.math3.stat.descriptive.rank.Percentile;
35  import org.apache.commons.math3.stat.descriptive.summary.Sum;
36  import org.apache.commons.math3.stat.descriptive.summary.SumOfSquares;
37  import org.apache.commons.math3.util.MathUtils;
38  import org.apache.commons.math3.util.ResizableDoubleArray;
39  import org.apache.commons.math3.util.FastMath;
40  
41  
42  /**
43   * Maintains a dataset of values of a single variable and computes descriptive
44   * statistics based on stored data. The {@link #getWindowSize() windowSize}
45   * property sets a limit on the number of values that can be stored in the
46   * dataset.  The default value, INFINITE_WINDOW, puts no limit on the size of
47   * the dataset.  This value should be used with caution, as the backing store
48   * will grow without bound in this case.  For very large datasets,
49   * {@link SummaryStatistics}, which does not store the dataset, should be used
50   * instead of this class. If <code>windowSize</code> is not INFINITE_WINDOW and
51   * more values are added than can be stored in the dataset, new values are
52   * added in a "rolling" manner, with new values replacing the "oldest" values
53   * in the dataset.
54   *
55   * <p>Note: this class is not threadsafe.  Use
56   * {@link SynchronizedDescriptiveStatistics} if concurrent access from multiple
57   * threads is required.</p>
58   *
59   */
60  public class DescriptiveStatistics implements StatisticalSummary, Serializable {
61  
62      /**
63       * Represents an infinite window size.  When the {@link #getWindowSize()}
64       * returns this value, there is no limit to the number of data values
65       * that can be stored in the dataset.
66       */
67      public static final int INFINITE_WINDOW = -1;
68  
69      /** Serialization UID */
70      private static final long serialVersionUID = 4133067267405273064L;
71  
72      /** Name of the setQuantile method. */
73      private static final String SET_QUANTILE_METHOD_NAME = "setQuantile";
74  
75      /** hold the window size **/
76      protected int windowSize = INFINITE_WINDOW;
77  
78      /**
79       *  Stored data values
80       */
81      private ResizableDoubleArray eDA = new ResizableDoubleArray();
82  
83      /** Mean statistic implementation - can be reset by setter. */
84      private UnivariateStatistic meanImpl = new Mean();
85  
86      /** Geometric mean statistic implementation - can be reset by setter. */
87      private UnivariateStatistic geometricMeanImpl = new GeometricMean();
88  
89      /** Kurtosis statistic implementation - can be reset by setter. */
90      private UnivariateStatistic kurtosisImpl = new Kurtosis();
91  
92      /** Maximum statistic implementation - can be reset by setter. */
93      private UnivariateStatistic maxImpl = new Max();
94  
95      /** Minimum statistic implementation - can be reset by setter. */
96      private UnivariateStatistic minImpl = new Min();
97  
98      /** Percentile statistic implementation - can be reset by setter. */
99      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 }