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