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>