001 package com.softnetConsult.utils.math;
002
003 import java.util.Collections;
004 import java.util.Comparator;
005 import java.util.Iterator;
006 import java.util.Set;
007 import java.util.TreeMap;
008 import java.util.NavigableMap;
009 import java.util.Map.Entry;
010
011 import com.softnetConsult.utils.collections.Pair;
012
013
014 /**
015 * This class implements a sample distribution; it is a useful tool when undertaking a
016 * statistical analysis of value producing processes: it counts the number of times each
017 * value is observed (method {@link #observe(Number)}) and can be used conveniently for
018 * obtaining the number of observations within a certain range, discretising the observed
019 * sample, calculating mean, variance and other sample properties, printing and plotting
020 * the distribution, and so on.<br />
021 * Where possible, the methods provided by the class type of the sample values are used
022 * directly, for other computations the values are converted to {@code double}; be careful
023 * as this might have consequences if the type of the sampled variable cannot be exactly
024 * converted to {@code double}.
025 *
026 * <p style="font-size:smaller;">This product includes software developed by the
027 * <strong>SoftNet-Consult Java Utility Library</strong> project and its contributors.<br />
028 * (<a href="http://java-tools.sourceforge.net" target="_blank">http://java-tools.sourceforge.net</a>)<br />
029 * Copyright (c) 2007-2008 SoftNet-Consult.<br />
030 * Copyright (c) 2007-2008 G. Paperin.<br />
031 * All rights reserved.
032 * </p>
033 * <p style="font-size:smaller;">File: Distribution.java<br />
034 * Library API version: {@value com.softnetConsult.utils.APIProperties#apiVersion}<br />
035 * Java compliance version: {@value com.softnetConsult.utils.APIProperties#javaComplianceVersion}
036 * </p>
037 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
038 * without modification, are permitted provided that the following terms and conditions are met:
039 * </p>
040 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
041 * acknowledgement of the SoftNet-Consult Java Utility Library project, the above copyright
042 * notice, this list of conditions and the following disclaimer.<br />
043 * 2. Redistributions in binary form must reproduce the above acknowledgement of the
044 * SoftNet-Consult Java Utility Library project, the above copyright notice, this list of
045 * conditions and the following disclaimer in the documentation and/or other materials
046 * provided with the distribution.<br />
047 * 3. All advertising materials mentioning features or use of this software or any derived
048 * software must display the following acknowledgement:<br />
049 * <em>This product includes software developed by the SoftNet-Consult Java Utility Library
050 * project and its contributors.</em>
051 * </p>
052 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
053 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
054 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
055 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
056 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
057 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
058 * </p>
059 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
060 * @version {@value com.softnetConsult.utils.APIProperties#apiVersion}
061 *
062 * @param <T> The type of values of which the distribution is being analysed.
063 */
064 public class Distribution<T extends Number> {
065
066 /**
067 * Stores the observed sample (MAP: observation -> number of times observed).
068 */
069 private TreeMap<T, Integer> dist;
070
071 /**
072 * Smallest observation in this sample.
073 */
074 private T minObservation;
075
076 /**
077 * Largest observation in this sample.
078 */
079 private T maxObservation;
080
081 /**
082 * Number of observations in this sample.
083 */
084 private long observationCount;
085
086 /**
087 * Sample mean.
088 */
089 private double mean;
090
091 /**
092 * Sample variance.
093 */
094 private double variance;
095
096
097 /**
098 * A comparator object for all {@code Number} types. This comparator uses
099 * {@link MathTools#compare(Number, Number)} for the camparison.
100 */
101 private static final Comparator<Number> comparator = new Comparator<Number>() {
102 public int compare(Number x, Number y) {
103 return MathTools.compare(x, y);
104 }
105 };
106
107 private static String newLine = null;
108 private static String getNewLine() {
109 if (null == newLine)
110 newLine = String.format("%n");
111 return newLine;
112 }
113
114
115 /**
116 * Creates a new empty distribution sample.
117 */
118 public Distribution() {
119 this.dist = new TreeMap<T, Integer>(comparator);
120 this.minObservation = null;
121 this.maxObservation = null;
122 this.observationCount = 0L;
123 this.mean = Double.NaN;
124 this.variance = Double.NaN;
125 }
126
127
128 /**
129 * Used internally for creating discretised sammples and sub-samples of this distribution.
130 *
131 * @param dist The map containg the observation of this distribution.
132 */
133 private Distribution(TreeMap<T, Integer> dist) {
134 this.dist = dist;
135 if (dist.isEmpty()) {
136 this.minObservation = null;
137 this.maxObservation = null;
138 this.observationCount = 0L;
139 } else {
140 this.minObservation = dist.firstKey();
141 this.maxObservation = dist.lastKey();
142 this.observationCount = 0L;
143 for (Integer count : dist.values())
144 this.observationCount += count.intValue();
145 }
146 this.mean = Double.NaN;
147 this.variance = Double.NaN;
148 }
149
150 /**
151 * Adds an observation to this sample.
152 *
153 * @param observation The observed value.
154 */
155 public void observe(T observation) {
156
157 if (null == observation)
158 throw new NullPointerException("null observation is not allowed");
159
160 if (dist.containsKey(observation)) {
161 dist.put(observation, dist.get(observation) + 1);
162 } else {
163 dist.put(observation, 1);
164 }
165
166 observationCount++;
167 mean = Double.NaN;
168 variance = Double.NaN;
169
170 if (minObservation == null || MathTools.compare(observation, minObservation) < 0)
171 minObservation = observation;
172
173 if (maxObservation == null || MathTools.compare(observation, maxObservation) > 0)
174 maxObservation = observation;
175 }
176
177
178 /**
179 * Discretises this sample into intervals of the specified length. This is equivalent to
180 * {@code discretise(getMin(), getMax(), interval)}.
181 *
182 * @param interval The size of the discrete intervals.
183 * @return A discretised distribution containing the same observations as this distribution.
184 * @see #discretise(Number, Number, double)
185 */
186 public Distribution<Double> discretise(double interval) {
187 return discretise(minObservation, maxObservation, interval);
188 }
189
190
191 /**
192 * Discretises this sample into intervals of the specified length while only considering
193 * the observations between {@code min} and {@code max} (inclusive).<br />
194 * <br />
195 * If {@code S = (max - min) / interval}, the resulting distribution will contain {@code S}
196 * observations if {@code (max - min)} is not exactly dividable by {@code interval}, otherwise
197 * it will contain {@code S + 1} observations.<br />
198 * The first observation of the resulting distrinution will be {@code min} and the
199 * corresponding frequency will be the sum of the frequencies of all observations between
200 * {@code min} (inclusive) and {@code min + inverval} (exclusive) in this original distribution.<br />
201 * The n-th observation of the resulting distrinution will be {@code (min + (n-1) * interval} and
202 * the corresponding frequency will be the sum of the frequencies of all observations between
203 * {@code min + (n-1) * inverval} (inclusive) and {@code min + n * inverval} (exclusive) in this
204 * original distribution. No observations of this original distribution that are larger than
205 * the specified {@code max} will be considered.<br />
206 * <br />
207 * If {@code min} > {@code max} the resulting distribution will be empty.<br />
208 * This method converts the observations contained in this sample to {@code double}; be careful
209 * as this might have consequences if the type of the values in this sample cannot be exactly
210 * converted to {@code double}. Be also aware of the "{@code 1 + 1 = 1.9999999}" effect which may
211 * cause inconsistencies between this and disretised distribution.
212 *
213 * @param min The minimum of the observation interval to be discretised.
214 * @param max The maximum of the observation interval to be discretised.
215 * @param interval The size of the discrete intervals.
216 * @return A discretised sample as described above.
217 */
218 public Distribution<Double> discretise(T min, T max, double interval) {
219
220 if (interval <= 0.0)
221 throw new IllegalArgumentException("interval <= 0.0 is not allowed");
222
223 TreeMap<Double, Integer> discreteDist = new TreeMap<Double, Integer>(comparator);
224 NavigableMap<T, Integer> view = dist.subMap(min, true, max, true);
225 double maxD = max.doubleValue();
226
227 double group = min.doubleValue();
228 while (group <= maxD) {
229 int count = 0;
230 boolean done = false;
231 Iterator<Entry<T, Integer>> it = view.entrySet().iterator();
232 Entry<T, Integer> entry;
233 double observ;
234 while(it.hasNext() && !done) {
235 entry = it.next();
236 observ = entry.getKey().doubleValue();
237 done = group + interval <= observ;
238 if (group <= observ && !done)
239 count += entry.getValue().intValue();
240 }
241 discreteDist.put(group, count);
242
243 group += interval;
244 }
245
246 return new Distribution<Double>(discreteDist);
247 }
248
249 /**
250 * Discretises this sample into intervals of the specified length. This is equivalent to
251 * {@code discretise(getMin(), getMax(), interval)}.
252 *
253 * @param interval The size of the discrete intervals.
254 * @return A discretised distribution containing the same observations as this distribution.
255 * @see #discretise(Number, Number, int)
256 */
257 public Distribution<Integer> discretise(int interval) {
258 return discretise(minObservation, maxObservation, interval);
259 }
260
261 /**
262 * Discretises this sample into intervals of the specified length while only considering
263 * the observations between {@code min} and {@code max} (inclusive).<br />
264 * <br />
265 * If {@code S = (max - min) / interval}, the resulting distribution will contain {@code S}
266 * observations if {@code (max - min)} is not exactly dividable by {@code interval}, otherwise
267 * it will contain {@code S + 1} observations.<br />
268 * The first observation of the resulting distrinution will be {@code min} and the
269 * corresponding frequency will be the sum of the frequencies of all observations between
270 * {@code min} (inclusive) and {@code min + inverval} (exclusive) in this original distribution.<br />
271 * The n-th observation of the resulting distrinution will be {@code (min + (n-1) * interval} and
272 * the corresponding frequency will be the sum of the frequencies of all observations between
273 * {@code min + (n-1) * inverval} (inclusive) and {@code min + n * inverval} (exclusive) in this
274 * original distribution. No observations of this original distribution that are larger than
275 * the specified {@code max} will be considered.<br />
276 * <br />
277 * If {@code min} > {@code max} the resulting distribution will be empty.<br />
278 * This method converts the observations contained in this sample to {@code int}; be careful
279 * as this might have consequences if the type of the values in this sample cannot be exactly
280 * converted to {@code int}.
281 *
282 * @param min The minimum of the observation interval to be discretised.
283 * @param max The maximum of the observation interval to be discretised.
284 * @param interval The size of the discrete intervals.
285 * @return A discretised sample as described above.
286 */
287 public Distribution<Integer> discretise(T min, T max, int interval) {
288
289 if (interval <= 0)
290 throw new IllegalArgumentException("interval <= 0 is not allowed");
291
292 TreeMap<Integer, Integer> discreteDist = new TreeMap<Integer, Integer>(comparator);
293 NavigableMap<T, Integer> view = dist.subMap(min, true, max, true);
294 int maxI = max.intValue();
295
296 int group = min.intValue();
297 while (group <= maxI) {
298 int count = 0;
299 boolean done = false;
300 Iterator<Entry<T, Integer>> it = view.entrySet().iterator();
301 Entry<T, Integer> entry;
302 int observ;
303 while(it.hasNext() && !done) {
304 entry = it.next();
305 observ = entry.getKey().intValue();
306 done = group + interval <= observ;
307 if (group <= observ && !done)
308 count += entry.getValue().intValue();
309 }
310 discreteDist.put(group, count);
311
312 group += interval;
313 }
314 return new Distribution<Integer>(discreteDist);
315 }
316
317 /**
318 * A sample that contains only the values of this sample between the specified boundaries.
319 *
320 * @param min Min observation.
321 * @param max Max observation.
322 * @return A new {@code Distribution} that is the same as this distribution, but contains
323 * only observations between {@code min} and {@code max} (inclusive).
324 */
325 public Distribution<T> selectInterval(T min, T max) {
326
327 if (null == min || null == max)
328 throw new NullPointerException("min and max may not be null");
329
330 NavigableMap<T, Integer> view = dist.subMap(min, true, max, true);
331 TreeMap<T, Integer> intervalDist = new TreeMap<T, Integer>(view);
332 return new Distribution<T>(intervalDist);
333 }
334
335 /**
336 * Gets the number of times the specified observation was encountered.
337 *
338 * @param observation An observation value.
339 * @return The frequency of the speciefied observation.
340 */
341 public int countObservations(T observation) {
342 Integer count = dist.get(observation);
343 if (null == count)
344 return 0;
345 return count.intValue();
346 }
347
348 /**
349 * Gets the proportion of the specified observation out of all observations in this sample.
350 *
351 * @param observation An observation value.
352 * @return The frequency of the speciefied observation divided by the size of this sample.
353 */
354 public double countProportion(T observation) {
355 if (0L == observationCount)
356 return Double.NaN;
357 int count = countObservations(observation);
358 if (0 == count)
359 return 0.0;
360 return count / (double) observationCount;
361 }
362
363 /**
364 * Gets the total number of times that any observation within the specified range was encountered.
365 *
366 * @param min Min observation.
367 * @param max Max observation.
368 * @return The sum of the frequencies of all observations between {@code min}
369 * and {@code max} (inclusive).
370 */
371 public int countObservations(T min, T max) {
372 if (null == min || null == max)
373 throw new NullPointerException("min and max may not be null");
374
375 NavigableMap<T, Integer> view = dist.subMap(min, true, max, true);
376 int count = 0;
377 for (Integer c : view.values()) {
378 count += c.intValue();
379 }
380 return count;
381 }
382
383 /**
384 * Gets proportion of observations within the specified range out of all observations in this sample.
385 *
386 * @param min Min observation.
387 * @param max Max observation.
388 * @return The sum of the frequencies of all observations between {@code min} and {@code max}
389 * (inclusive) divided by the number of observations in this sample.
390 */
391 public double countProportion(T min, T max) {
392 if (0L == observationCount)
393 return Double.NaN;
394 int count = countObservations(min, max);
395 if (0 == count)
396 return 0.0;
397 return count / (double) observationCount;
398 }
399
400 /**
401 * Returns an unmodifiable {@code Set} view of the observations contained in this distribution sample.
402 * The set's iterator returns the keys in ascending order. The set is backed by the distributioon,
403 * so changes to the map are reflected in the set. If the map is modified while an iteration over
404 * the set is in progress, the results of the iteration are undefined.
405 *
406 * @return An unmodifiable {@code Set} view of the observations contained in this distribution sample.
407 */
408 public Set<T> getObservations() {
409 return Collections.unmodifiableSet(dist.keySet());
410 }
411
412 /**
413 * Computes the mean of this sample.
414 * This method converts the observations contained in this sample to {@code double} values;
415 * be careful as this might have consequences if the type of the values in this sample cannot
416 * be exactly converted to {@code double}.
417 *
418 * @return The mean of this sample.
419 */
420 public double getMean() {
421 if (!Double.isNaN(mean))
422 return mean;
423
424 if (1L > observationCount)
425 return Double.NaN;
426
427 mean = 0.0;
428 for (Entry<T, Integer> entry : dist.entrySet()) {
429 mean += entry.getKey().doubleValue() * entry.getValue().doubleValue();
430 }
431 mean /= observationCount;
432 return mean;
433 }
434
435
436 /**
437 * Computes the variance of this sample.
438 * This method converts the observations contained in this sample to {@code double} values;
439 * be careful as this might have consequences if the type of the values in this sample cannot
440 * be exactly converted to {@code double}.
441 *
442 * @return The variance of this sample.
443 */
444 public double getVariance() {
445 if (!Double.isNaN(variance))
446 return variance;
447
448 if (2L > observationCount)
449 return Double.NaN;
450
451 double m = getMean();
452 variance = 0.0;
453 for (Entry<T, Integer> entry : dist.entrySet()) {
454 double d = (entry.getKey().doubleValue() - m);
455 variance += d * d * entry.getValue().doubleValue();
456 }
457 variance /= (observationCount - 1);
458 return variance;
459 }
460
461
462 /**
463 * Returns a new Distribution in which each observation frequency equals to the observation frequency
464 * of this distribution divided by the specified value;
465 * all resulting non-integer frequencies are rounded to the nearest integer.
466 *
467 * @param value A non-zero value.
468 * @return A new distribution that in normalised through dividing by the specified value.
469 */
470 public Distribution<T> normaliseBy(double value) {
471
472 if (0 == value)
473 throw new IllegalArgumentException("Cannot normalise by 0");
474
475 TreeMap<T, Integer> normalised = new TreeMap<T, Integer>();
476 for (Entry<T, Integer> entry : dist.entrySet()) {
477
478 int normFreq = (int) Math.round(entry.getValue().intValue() / value);
479 if (0 < normFreq)
480 normalised.put(entry.getKey(), normFreq);
481 }
482
483 return new Distribution<T>(normalised);
484 }
485
486 /**
487 * Returns a new Distribution in which each observation frequency equals to the logarithm of
488 * the corresponding observation frequency of this distribution;
489 * all resulting non-integer frequencies are rounded to the nearest integer.
490 *
491 * @param base The base of the logarithm to use in obtainign the new distribution sample.
492 * @return A new log distribution obtained from this distribution.
493 */
494 public Distribution<T> getLogDistribution(double base) {
495
496 if (Double.isNaN(base) || Double.isInfinite(base))
497 throw new IllegalArgumentException("Log base must be a real positive number");
498 if (0 >= base)
499 throw new IllegalArgumentException("Log base must be positive");
500
501 TreeMap<T, Integer> logDist = new TreeMap<T, Integer>();
502 for (Entry<T, Integer> entry : dist.entrySet()) {
503
504 int logFreq = (int) Math.round(MathTools.log(base, (double) entry.getValue().intValue()));
505 if (0 < logFreq)
506 logDist.put(entry.getKey(), logFreq);
507 }
508
509 return new Distribution<T>(logDist);
510 }
511
512 /**
513 * Gets the data of this distribution as two arrays - one containing the observations, and
514 * the other containing the corresponding frequencies.
515 *
516 * @return A {@code Pair} of {@code array}s, where the first element of the pair is an
517 * array contaning all observations in this sample in ascending order and the second
518 * element of the pair is an array containing the respective observation frequencies.
519 */
520 public Pair<T[], Integer[]> getData() {
521
522 @SuppressWarnings("unchecked")
523 T[] X = (T[]) new Number[dist.size()];
524
525 Integer[] Y = new Integer[dist.size()];
526
527 int i = 0;
528 for (Entry<T, Integer> entry : dist.entrySet()) {
529 X[i] = entry.getKey();
530 Y[i] = entry.getValue();
531 i++;
532 }
533 return new Pair<T[], Integer[]>(X, Y);
534 }
535
536 /**
537 * Computes the standard deviation of this sample.
538 * This method converts the observations contained in this sample to {@code double} values;
539 * be careful as this might have consequences if the type of the values in this sample cannot
540 * be exactly converted to {@code double}.
541 *
542 * @return The standard deviation of this sample.
543 */
544 public double getStdDeviation() {
545 return Math.sqrt(variance);
546 }
547
548
549 /**
550 * Gets the smallest observation in this sample.
551 *
552 * @return The smallest observation in this sample or {@code null} is this sample is empty.
553 */
554 public T getMin() {
555 return minObservation;
556 }
557
558
559 /**
560 * Gets the largest observation in this sample.
561 *
562 * @return The largest observation in this sample or {@code null} is this sample is empty.
563 */
564 public T getMax() {
565 return maxObservation;
566 }
567
568
569 /**
570 * Gets the total number of observations in this sample.
571 * If this sample is very large, it may be prefereable to use {@link #countObservationsL()}.
572 *
573 * @return The size of this sample.
574 */
575 public int countObservations() {
576 return (int) observationCount;
577 }
578
579 /**
580 * Gets the total number of observations in this sample.
581 *
582 * @return The size of this sample.
583 */
584 public long countObservationsL() {
585 return observationCount;
586 }
587
588 /**
589 * Creates a {@code String} that contains some basic statistical information about this sample,
590 * such as the number of observations, mean, variance and standard deviation.
591 *
592 * @return A {@code String} that contains some basic statistical information about this sample.
593 */
594 public String getStringStats() {
595 StringBuffer s = new StringBuffer();
596 s.append("OBSERVATIONS: \t");
597 s.append(observationCount);
598 s.append(getNewLine());
599 s.append("MEAN: \t");
600 s.append(getMean());
601 s.append(getNewLine());
602 s.append("VARIANCE: \t");
603 s.append(getVariance());
604 s.append(getNewLine());
605 s.append("STD DEVIATION:\t");
606 s.append(getStdDeviation());
607 s.append(getNewLine());
608 return s.toString();
609 }
610
611 /**
612 * Creates a {@code String} with a distribution table of this sample. The {@code toString()}
613 * method of the sample values is used and not type specific formating is performed.
614 *
615 * @param extraInfo Whether to append the result of {@link #getStringStats()} to the distribution
616 * table.
617 * @return A distribution table of this sample.
618 */
619 public String getStringTable(boolean extraInfo) {
620 StringBuffer s = new StringBuffer();
621 for (Entry<T, Integer> entry : dist.entrySet()) {
622 s.append(entry.getKey());
623 s.append(":\t");
624 s.append(entry.getValue());
625 s.append("\t (");
626 s.append(String.format("%08.5f", 100. * entry.getValue().doubleValue() / observationCount));
627 s.append("%)");
628 s.append(getNewLine());
629 }
630 if (extraInfo)
631 s.append(getStringStats());
632 return s.toString();
633 }
634
635 /**
636 * This is equivalent to {@code getStringTable(true)}.
637 *
638 * @return A distribution table of this sample.
639 */
640 public String getStringTable() {
641 return getStringTable(true);
642 }
643
644
645 /**
646 * Creates a {@code String} with a distribution table of this sample. The observation values
647 * are formatted as {@code double}s.
648 *
649 * @param extraInfo Whether to append the result of {@link #getStringStats()} to the distribution
650 * table.
651 * @return A distribution table of this sample.
652 */
653 public String getStringTableD(boolean extraInfo) {
654 StringBuffer s = new StringBuffer();
655 for (Entry<T, Integer> entry : dist.entrySet()) {
656 int count = entry.getValue();
657 s.append(String.format("%18.010f: %11d (%08.5f%%)%n",
658 entry.getKey().doubleValue(), count, 100.0 * count / observationCount));
659 }
660 if (extraInfo)
661 s.append(getStringStats());
662 return s.toString();
663 }
664
665 /**
666 * This is equivalent to {@code getStringTableD(true)}.
667 *
668 * @return A distribution table of this sample.
669 */
670 public String getStringTableD() {
671 return getStringTableD(true);
672 }
673
674
675 /**
676 * Creates a {@code String} with a distribution table of this sample. The observation values
677 * are formatted as {@code int}s.
678 *
679 * @param extraInfo Whether to append the result of {@link #getStringStats()} to the distribution
680 * table.
681 * @return A distribution table of this sample.
682 */
683 public String getStringTableI(boolean extraInfo) {
684 StringBuffer s = new StringBuffer();
685 for (Entry<T, Integer> entry : dist.entrySet()) {
686 int count = entry.getValue();
687 s.append(String.format("%11d: %11d (%08.5f%%)%n",
688 entry.getKey().intValue(), count, 100.0 * count / observationCount));
689 }
690 if (extraInfo)
691 s.append(getStringStats());
692 return s.toString();
693 }
694
695
696 /**
697 * This is equivalent to {@code getStringTableI(true)}.
698 *
699 * @return A distribution table of this sample.
700 */
701 public String getStringTableI() {
702 return getStringTableI(true);
703 }
704
705
706 /**
707 * This method creates a string that - if saved to a file - can be loaded by the
708 * LiveGraph-plotter in order to plot this dirtribution sample.<br />
709 * <br />
710 * The LiveGraph plotter framework is an open-source project written in Java available from
711 * <a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>.<br />
712 * <br />
713 * The string returned by this method encodes all observations of this sample. For a
714 * straight-forward method of saving the string to a file, see
715 * {@link com.softnetConsult.utils.files.FileTools#writeToFile(String, String, boolean)}.
716 *
717 * @return An encoding of this distribution to be used with the LiveGraph plotter.
718 */
719 public String getStringLiveGraphPlot() {
720 return getStringLiveGraphPlot((String[]) null);
721 }
722
723 /**
724 * This method creates a string that - if saved to a file - can be loaded by the
725 * LiveGraph-plotter in order to plot this dirtribution sample.<br />
726 * <br />
727 * The LiveGraph plotter framework is an open-source project written in Java available from
728 * <a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>.<br />
729 * <br />
730 * The string returned by this method encodes all observations of this sample. For a
731 * straight-forward method of saving the string to a file, see
732 * {@link com.softnetConsult.utils.files.FileTools#writeToFile(String, String, boolean)}.
733 *
734 * @param info An info string to add as data file annotation.
735 * @return An encoding of this distribution to be used with the LiveGraph plotter.
736 */
737 public String getStringLiveGraphPlot(final String info) {
738 return getStringLiveGraphPlot(new String[] {info});
739 }
740
741 /**
742 * This method creates a string that - if saved to a file - can be loaded by the
743 * LiveGraph-plotter in order to plot this dirtribution sample.<br />
744 * <br />
745 * The LiveGraph plotter framework is an open-source project written in Java available from
746 * <a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>.<br />
747 * <br />
748 * The string returned by this method encodes all observations of this sample. For a
749 * straight-forward method of saving the string to a file, see
750 * {@link com.softnetConsult.utils.files.FileTools#writeToFile(String, String, boolean)}.
751 *
752 * @param infos An list of info strings to add as data file annotation.
753 * @return An encoding of this distribution to be used with the LiveGraph plotter.
754 */
755 public String getStringLiveGraphPlot(final String[] infos) {
756 StringBuffer s = new StringBuffer();
757 s.append("@Distribution Plot");
758 s.append(getNewLine());
759 s.append("@Generated by com.softnetConsult.utils.math.Distribution");
760 s.append(getNewLine());
761 s.append("@(see http://java-tools.sourceforge.net/)");
762 s.append(getNewLine());
763 if (null != infos && 0 < infos.length) {
764 s.append("@ ");
765 s.append(getNewLine());
766 for (String info : infos) {
767 s.append("@ ");
768 s.append(null == info ? "" : info);
769 s.append(getNewLine());
770 }
771 }
772 s.append("@ ");
773 s.append(getNewLine());
774 s.append("@Observations: \t");
775 s.append(observationCount);
776 s.append(getNewLine());
777 s.append("@Mean: \t");
778 s.append(getMean());
779 s.append(getNewLine());
780 s.append("@Variance: \t");
781 s.append(getVariance());
782 s.append(getNewLine());
783 s.append("@Std Deviation:\t");
784 s.append(getStdDeviation());
785 s.append(getNewLine());
786 s.append("Observation, Frequency, Per Cent");
787 s.append(getNewLine());
788 for (Entry<T, Integer> entry : dist.entrySet()) {
789 s.append(entry.getKey());
790 s.append(", ");
791 s.append(entry.getValue());
792 s.append(", ");
793 s.append(100.0 * entry.getValue().doubleValue() / observationCount);
794 s.append(getNewLine());
795 }
796 return s.toString();
797 }
798
799 } // public class Distribution<T extends Number>