/src/Numerics/Statistics/Percentile.cs

http://mathnetnumerics.codeplex.com · C# · 210 lines · 100 code · 22 blank · 88 comment · 12 complexity · 5ac09c524e40c8143d9923d07df6d94a MD5 · raw file

  1. // <copyright file="Percentile.cs" company="Math.NET">
  2. // Math.NET Numerics, part of the Math.NET Project
  3. // http://numerics.mathdotnet.com
  4. // http://github.com/mathnet/mathnet-numerics
  5. // http://mathnetnumerics.codeplex.com
  6. // Copyright (c) 2009-2010 Math.NET
  7. // Permission is hereby granted, free of charge, to any person
  8. // obtaining a copy of this software and associated documentation
  9. // files (the "Software"), to deal in the Software without
  10. // restriction, including without limitation the rights to use,
  11. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. // copies of the Software, and to permit persons to whom the
  13. // Software is furnished to do so, subject to the following
  14. // conditions:
  15. // The above copyright notice and this permission notice shall be
  16. // included in all copies or substantial portions of the Software.
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  19. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  23. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  24. // OTHER DEALINGS IN THE SOFTWARE.
  25. // </copyright>
  26. namespace MathNet.Numerics.Statistics
  27. {
  28. using System;
  29. using System.Collections.Generic;
  30. using System.Linq;
  31. /// <summary>
  32. /// Methods to calculate the percentiles.
  33. /// </summary>
  34. public enum PercentileMethod
  35. {
  36. /// <summary>
  37. /// Using the method recommened my NIST,
  38. /// http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm
  39. /// </summary>
  40. Nist = 0,
  41. /// <summary>
  42. /// Using the nearest rank, http://en.wikipedia.org/wiki/Percentile#Nearest_Rank
  43. /// </summary>
  44. Nearest,
  45. /// <summary>
  46. /// Using the same method as Excel does,
  47. /// http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm
  48. /// </summary>
  49. Excel,
  50. /// <summary>
  51. /// Use linear interpolation between the two nearest ranks,
  52. /// http://en.wikipedia.org/wiki/Percentile#Linear_Interpolation_Between_Closest_Ranks
  53. /// </summary>
  54. Interpolation
  55. }
  56. /// <summary>
  57. /// Class to calculate percentiles.
  58. /// </summary>
  59. public class Percentile
  60. {
  61. /// <summary>
  62. /// Holds the data.
  63. /// </summary>
  64. private readonly List<double> _data;
  65. /// <summary>
  66. /// Gets or sets the method used to calculate the percentiles.
  67. /// </summary>
  68. /// <value>The calculation method.</value>
  69. /// <remarks>defaults to <see cref="PercentileMethod.Nist"/>.</remarks>
  70. public PercentileMethod Method
  71. {
  72. get;
  73. set;
  74. }
  75. /// <summary>
  76. /// Initializes a new instance of the <see cref="Percentile"/> class.
  77. /// </summary>
  78. /// <param name="data">The data to calculate the percentiles of.</param>
  79. public Percentile(IEnumerable<double> data)
  80. {
  81. if (data == null)
  82. {
  83. throw new ArgumentNullException("data");
  84. }
  85. if (data.Count() < 3)
  86. {
  87. throw new ArgumentException(string.Format(Properties.Resources.MustContainAtLeast, 3), "data");
  88. }
  89. _data = new List<double>(data);
  90. _data.Sort();
  91. }
  92. /// <summary>
  93. /// Computes the percentile.
  94. /// </summary>
  95. /// <param name="percentile">The percentile, must be between 0.0 and 1.0 (inclusive).</param>
  96. /// <returns>the requested percentile.</returns>
  97. public double Compute(double percentile)
  98. {
  99. if (percentile < 0 || percentile > 100)
  100. {
  101. throw new ArgumentException("Percentile value must be between 0 and 100.");
  102. }
  103. if (percentile == 0.0)
  104. {
  105. return _data[0];
  106. }
  107. if (percentile == 1.0)
  108. {
  109. return _data[_data.Count - 1];
  110. }
  111. var result = double.NaN;
  112. switch (Method)
  113. {
  114. case PercentileMethod.Nist:
  115. result = Nist(percentile);
  116. break;
  117. case PercentileMethod.Nearest:
  118. result = Nearest(percentile);
  119. break;
  120. case PercentileMethod.Interpolation:
  121. result = Interpolation(percentile);
  122. break;
  123. case PercentileMethod.Excel:
  124. result = Excel(percentile);
  125. break;
  126. }
  127. return result;
  128. }
  129. /// <summary>
  130. /// Computes the percentiles for the given list.
  131. /// </summary>
  132. /// <param name="percentiles">The percentiles, must be between 0.0 and 1.0 (inclusive)</param>
  133. /// <returns>the values that correspond to the given percentiles.</returns>
  134. public IList<double> Compute(IEnumerable<double> percentiles)
  135. {
  136. if (percentiles == null)
  137. {
  138. throw new ArgumentNullException("percentiles");
  139. }
  140. return percentiles.Select(Compute).ToList();
  141. }
  142. /// <summary>
  143. /// Computes the percentile using the nearest value.
  144. /// </summary>
  145. /// <param name="percentile">The percentile.</param>
  146. /// <returns>the percentile using the nearest value.</returns>
  147. private double Nearest(double percentile)
  148. {
  149. var n = (int)Math.Round((_data.Count * percentile) + 0.5, 0);
  150. return _data[n - 1];
  151. }
  152. /// <summary>
  153. /// Computes the percentile using Excel's method.
  154. /// </summary>
  155. /// <param name="percentile">The percentile.</param>
  156. /// <returns>the percentile using Excel's method.</returns>
  157. private double Excel(double percentile)
  158. {
  159. var tmp = 1 + (percentile * (_data.Count - 1.0));
  160. var k = (int)tmp;
  161. var d = tmp - k;
  162. return _data[k - 1] + (d * (_data[k] - _data[k - 1]));
  163. }
  164. /// <summary>
  165. /// Computes the percentile using interpolation.
  166. /// </summary>
  167. /// <param name="percentile">The percentile.</param>
  168. /// <returns>the percentile using the interpolation.</returns>
  169. private double Interpolation(double percentile)
  170. {
  171. var k = (int)(_data.Count * percentile);
  172. var pk = (k - 0.5) / _data.Count;
  173. return _data[k - 1] + (_data.Count * (percentile - pk) * (_data[k] - _data[k - 1]));
  174. }
  175. /// <summary>
  176. /// Computes the percentile using NIST's method.
  177. /// </summary>
  178. /// <param name="percentile">The percentile.</param>
  179. /// <returns>the percentile using NIST's method.</returns>
  180. private double Nist(double percentile)
  181. {
  182. var tmp = percentile * (_data.Count + 1.0);
  183. var k = (int)tmp;
  184. var d = tmp - k;
  185. return _data[k - 1] + (d * (_data[k] - _data[k - 1]));
  186. }
  187. }
  188. }