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.math.stat; 18 19 import java.io.Serializable; 20 import java.text.NumberFormat; 21 import java.util.Iterator; 22 import java.util.Comparator; 23 import java.util.TreeMap; 24 25 /** 26 * Maintains a frequency distribution. 27 * <p> 28 * Accepts int, long, char or Object values. New values added must be 29 * comparable to those that have been added, otherwise the add method will 30 * throw an IllegalArgumentException.</p> 31 * <p> 32 * Integer values (int, long, Integer, Long) are not distinguished by type -- 33 * i.e. <code>addValue(new Long(2)), addValue(2), addValue(2l)</code> all have 34 * the same effect (similarly for arguments to <code>getCount,</code> etc.).</p> 35 * <p> 36 * The values are ordered using the default (natural order), unless a 37 * <code>Comparator</code> is supplied in the constructor.</p> 38 * 39 * @version $Revision: 617953 $ $Date: 2008-02-02 22:54:00 -0700 (Sat, 02 Feb 2008) $ 40 */ 41 public class Frequency implements Serializable { 42 43 /** Serializable version identifier */ 44 private static final long serialVersionUID = -3845586908418844111L; 45 46 /** underlying collection */ 47 private TreeMap freqTable = null; 48 49 /** 50 * Default constructor. 51 */ 52 public Frequency() { 53 freqTable = new TreeMap(); 54 } 55 56 /** 57 * Constructor allowing values Comparator to be specified. 58 * 59 * @param comparator Comparator used to order values 60 */ 61 public Frequency(Comparator comparator) { 62 freqTable = new TreeMap(comparator); 63 } 64 65 /** 66 * Return a string representation of this frequency 67 * distribution. 68 * 69 * @return a string representation. 70 */ 71 public String toString() { 72 NumberFormat nf = NumberFormat.getPercentInstance(); 73 StringBuffer outBuffer = new StringBuffer(); 74 outBuffer.append("Value \t Freq. \t Pct. \t Cum Pct. \n"); 75 Iterator iter = freqTable.keySet().iterator(); 76 while (iter.hasNext()) { 77 Object value = iter.next(); 78 outBuffer.append(value); 79 outBuffer.append('\t'); 80 outBuffer.append(getCount(value)); 81 outBuffer.append('\t'); 82 outBuffer.append(nf.format(getPct(value))); 83 outBuffer.append('\t'); 84 outBuffer.append(nf.format(getCumPct(value))); 85 outBuffer.append('\n'); 86 } 87 return outBuffer.toString(); 88 } 89 90 /** 91 * Adds 1 to the frequency count for v. 92 * 93 * @param v the value to add. 94 * @throws IllegalArgumentException if <code>v</code> is not comparable. 95 */ 96 public void addValue(Object v) { 97 Object obj = v; 98 if (v instanceof Integer) { 99 obj = new Long(((Integer) v).longValue()); 100 } 101 try { 102 Long count = (Long) freqTable.get(obj); 103 if (count == null) { 104 freqTable.put(obj, new Long(1)); 105 } else { 106 freqTable.put(obj, new Long(count.longValue() + 1)); 107 } 108 } catch (ClassCastException ex) { 109 //TreeMap will throw ClassCastException if v is not comparable 110 throw new IllegalArgumentException("Value not comparable to existing values."); 111 } 112 } 113 114 /** 115 * Adds 1 to the frequency count for v. 116 * 117 * @param v the value to add. 118 */ 119 public void addValue(int v) { 120 addValue(new Long(v)); 121 } 122 123 /** 124 * Adds 1 to the frequency count for v. 125 * 126 * @param v the value to add. 127 */ 128 public void addValue(Integer v) { 129 addValue(new Long(v.longValue())); 130 } 131 132 /** 133 * Adds 1 to the frequency count for v. 134 * 135 * @param v the value to add. 136 */ 137 public void addValue(long v) { 138 addValue(new Long(v)); 139 } 140 141 /** 142 * Adds 1 to the frequency count for v. 143 * 144 * @param v the value to add. 145 */ 146 public void addValue(char v) { 147 addValue(new Character(v)); 148 } 149 150 /** Clears the frequency table */ 151 public void clear() { 152 freqTable.clear(); 153 } 154 155 /** 156 * Returns an Iterator over the set of values that have been added. 157 * <p> 158 * If added values are itegral (i.e., integers, longs, Integers, or Longs), 159 * they are converted to Longs when they are added, so the objects returned 160 * by the Iterator will in this case be Longs.</p> 161 * 162 * @return values Iterator 163 */ 164 public Iterator valuesIterator() { 165 return freqTable.keySet().iterator(); 166 } 167 168 //------------------------------------------------------------------------- 169 170 /** 171 * Returns the sum of all frequencies. 172 * 173 * @return the total frequency count. 174 */ 175 public long getSumFreq() { 176 long result = 0; 177 Iterator iterator = freqTable.values().iterator(); 178 while (iterator.hasNext()) { 179 result += ((Long) iterator.next()).longValue(); 180 } 181 return result; 182 } 183 184 /** 185 * Returns the number of values = v. 186 * 187 * @param v the value to lookup. 188 * @return the frequency of v. 189 */ 190 public long getCount(Object v) { 191 if (v instanceof Integer) { 192 return getCount(((Integer) v).longValue()); 193 } 194 long result = 0; 195 try { 196 Long count = (Long) freqTable.get(v); 197 if (count != null) { 198 result = count.longValue(); 199 } 200 } catch (ClassCastException ex) { 201 // ignore and return 0 -- ClassCastException will be thrown if value is not comparable 202 } 203 return result; 204 } 205 206 /** 207 * Returns the number of values = v. 208 * 209 * @param v the value to lookup. 210 * @return the frequency of v. 211 */ 212 public long getCount(int v) { 213 return getCount(new Long(v)); 214 } 215 216 /** 217 * Returns the number of values = v. 218 * 219 * @param v the value to lookup. 220 * @return the frequency of v. 221 */ 222 public long getCount(long v) { 223 return getCount(new Long(v)); 224 } 225 226 /** 227 * Returns the number of values = v. 228 * 229 * @param v the value to lookup. 230 * @return the frequency of v. 231 */ 232 public long getCount(char v) { 233 return getCount(new Character(v)); 234 } 235 236 //------------------------------------------------------------- 237 238 /** 239 * Returns the percentage of values that are equal to v 240 * (as a proportion between 0 and 1). 241 * <p> 242 * Returns <code>Double.NaN</code> if no values have been added.</p> 243 * 244 * @param v the value to lookup 245 * @return the proportion of values equal to v 246 */ 247 public double getPct(Object v) { 248 if (getSumFreq() == 0) { 249 return Double.NaN; 250 } 251 return (double) getCount(v) / (double) getSumFreq(); 252 } 253 254 /** 255 * Returns the percentage of values that are equal to v 256 * (as a proportion between 0 and 1). 257 * 258 * @param v the value to lookup 259 * @return the proportion of values equal to v 260 */ 261 public double getPct(int v) { 262 return getPct(new Long(v)); 263 } 264 265 /** 266 * Returns the percentage of values that are equal to v 267 * (as a proportion between 0 and 1). 268 * 269 * @param v the value to lookup 270 * @return the proportion of values equal to v 271 */ 272 public double getPct(long v) { 273 return getPct(new Long(v)); 274 } 275 276 /** 277 * Returns the percentage of values that are equal to v 278 * (as a proportion between 0 and 1). 279 * 280 * @param v the value to lookup 281 * @return the proportion of values equal to v 282 */ 283 public double getPct(char v) { 284 return getPct(new Character(v)); 285 } 286 287 //----------------------------------------------------------------------------------------- 288 289 /** 290 * Returns the cumulative frequency of values less than or equal to v. 291 * <p> 292 * Returns 0 if v is not comparable to the values set.</p> 293 * 294 * @param v the value to lookup. 295 * @return the proportion of values equal to v 296 */ 297 public long getCumFreq(Object v) { 298 if (getSumFreq() == 0) { 299 return 0; 300 } 301 if (v instanceof Integer) { 302 return getCumFreq(((Integer) v).longValue()); 303 } 304 Comparator c = freqTable.comparator(); 305 if (c == null) { 306 c = new NaturalComparator(); 307 } 308 long result = 0; 309 310 try { 311 Long value = (Long) freqTable.get(v); 312 if (value != null) { 313 result = value.longValue(); 314 } 315 } catch (ClassCastException ex) { 316 return result; // v is not comparable 317 } 318 319 if (c.compare(v, freqTable.firstKey()) < 0) { 320 return 0; // v is comparable, but less than first value 321 } 322 323 if (c.compare(v, freqTable.lastKey()) >= 0) { 324 return getSumFreq(); // v is comparable, but greater than the last value 325 } 326 327 Iterator values = valuesIterator(); 328 while (values.hasNext()) { 329 Object nextValue = values.next(); 330 if (c.compare(v, nextValue) > 0) { 331 result += getCount(nextValue); 332 } else { 333 return result; 334 } 335 } 336 return result; 337 } 338 339 /** 340 * Returns the cumulative frequency of values less than or equal to v. 341 * <p> 342 * Returns 0 if v is not comparable to the values set.</p> 343 * 344 * @param v the value to lookup 345 * @return the proportion of values equal to v 346 */ 347 public long getCumFreq(int v) { 348 return getCumFreq(new Long(v)); 349 } 350 351 /** 352 * Returns the cumulative frequency of values less than or equal to v. 353 * <p> 354 * Returns 0 if v is not comparable to the values set.</p> 355 * 356 * @param v the value to lookup 357 * @return the proportion of values equal to v 358 */ 359 public long getCumFreq(long v) { 360 return getCumFreq(new Long(v)); 361 } 362 363 /** 364 * Returns the cumulative frequency of values less than or equal to v. 365 * <p> 366 * Returns 0 if v is not comparable to the values set.</p> 367 * 368 * @param v the value to lookup 369 * @return the proportion of values equal to v 370 */ 371 public long getCumFreq(char v) { 372 return getCumFreq(new Character(v)); 373 } 374 375 //---------------------------------------------------------------------------------------------- 376 377 /** 378 * Returns the cumulative percentage of values less than or equal to v 379 * (as a proportion between 0 and 1). 380 * <p> 381 * Returns <code>Double.NaN</code> if no values have been added. 382 * Returns 0 if at least one value has been added, but v is not comparable 383 * to the values set.</p> 384 * 385 * @param v the value to lookup 386 * @return the proportion of values less than or equal to v 387 */ 388 public double getCumPct(Object v) { 389 if (getSumFreq() == 0) { 390 return Double.NaN; 391 } 392 return (double) getCumFreq(v) / (double) getSumFreq(); 393 } 394 395 /** 396 * Returns the cumulative percentage of values less than or equal to v 397 * (as a proportion between 0 and 1). 398 * <p> 399 * Returns 0 if v is not comparable to the values set.</p> 400 * 401 * @param v the value to lookup 402 * @return the proportion of values less than or equal to v 403 */ 404 public double getCumPct(int v) { 405 return getCumPct(new Long(v)); 406 } 407 408 /** 409 * Returns the cumulative percentage of values less than or equal to v 410 * (as a proportion between 0 and 1). 411 * <p> 412 * Returns 0 if v is not comparable to the values set.</p> 413 * 414 * @param v the value to lookup 415 * @return the proportion of values less than or equal to v 416 */ 417 public double getCumPct(long v) { 418 return getCumPct(new Long(v)); 419 } 420 421 /** 422 * Returns the cumulative percentage of values less than or equal to v 423 * (as a proportion between 0 and 1). 424 * <p> 425 * Returns 0 if v is not comparable to the values set.</p> 426 * 427 * @param v the value to lookup 428 * @return the proportion of values less than or equal to v 429 */ 430 public double getCumPct(char v) { 431 return getCumPct(new Character(v)); 432 } 433 434 /** 435 * A Comparator that compares comparable objects using the 436 * natural order. Copied from Commons Collections ComparableComparator. 437 */ 438 private static class NaturalComparator implements Comparator, Serializable { 439 440 /** Serializable version identifier */ 441 private static final long serialVersionUID = -3852193713161395148L; 442 443 /** 444 * Compare the two {@link Comparable Comparable} arguments. 445 * This method is equivalent to: 446 * <pre>(({@link Comparable Comparable})o1).{@link Comparable#compareTo compareTo}(o2)</pre> 447 * 448 * @param o1 the first object 449 * @param o2 the second object 450 * @return result of comparison 451 * @throws NullPointerException when <i>o1</i> is <code>null</code>, 452 * or when <code>((Comparable)o1).compareTo(o2)</code> does 453 * @throws ClassCastException when <i>o1</i> is not a {@link Comparable Comparable}, 454 * or when <code>((Comparable)o1).compareTo(o2)</code> does 455 */ 456 public int compare(Object o1, Object o2) { 457 return ((Comparable)o1).compareTo(o2); 458 } 459 } 460 }