百分位數 (Percentile) 跟 PR (Percentile Rank 百分等級) 是統計學常用參考值,年輕同學應該很不陌生(我們那個年代聯考比較簡單,直接用分數比大小,沒這麼多名堂),用考試分數比喻的話,百分位數是要在一百個等級中勝過幾個等級需要考幾分,或是位於某百分等級的人分數是幾分;PR 則是一個人的原始分數在團體依序被分為一百個等級的情況下,可以勝過多少等級。參考

我最近一次看到百分位數的場合是在壓力測試工具 - K6的報告,在 avg, max 外還用 P(90)、P(95) 評估效能:

最近在做資料庫評測,也想自己算百分位數,就研究了一下。

Excel 跟 Pythong NumPy 都有 Percentile 函數。Excel 的 PERCENTILE 已過時,等同 PERCENTILE.INC,另外還有個 PERCENTILE.EXC,二者差在 INC (Inclusive) 認為數據集的最小值和最大值分別是第 0 和第 100 百分位,然後此範圍內插值得到任何特定百分位數。EXC 則是排除型(Exclusive),認為數據集的最小值和最大值之間的任何點都可能是相應的百分位數,但最小值和最大值本身不包括在內。實測 NumPy 的 percentile() 結果同 PERCENTILE.INC:

如果要自己用 C# 算,公式要怎麼寫?我決定抄 Excel 的邏輯

  • 如果 array 是空值,PERCENTILE.INC 會傳回 #NUM! 錯誤值。
  • 如果 K 非數值,PERCENTILE.INC 會傳回 #VALUE! 錯誤值。
  • 如果 K < 0 或 K > 1,PERCENTILE.INC 會傳回 #NUM! 錯誤值。
  • 如果 K 不是 1/(n - 1) 的倍數,則 PERCENTILE.INC 會在陣列中內插一個中間值,以決定第 K 個百分位數的值。

寫成函式會像這樣:

var data = new List<int> { 2, 4, 4, 4, 6, 12, 25, 32, 48, 112 };
Console.WriteLine(GetPercentile(data, 25)); 
Console.WriteLine(GetPercentile(data, 50)); 
Console.WriteLine(GetPercentile(data, 90)); 
Console.WriteLine(GetPercentile(data, 95)); 
Console.WriteLine(GetPercentile(data, 99)); 
Console.WriteLine(GetPercentile(data, 100)); 

double GetPercentile(IEnumerable<int> data, float percent)
{
    if (data == null || !data.Any() || percent < 0 || percent > 100)
        throw new ArgumentException();
    var sorted = data.OrderBy(x => x).ToArray();
    var k = (sorted.Length - 1) * percent / 100.0;
    var i = (int)Math.Floor(k);
    var frac = k - i;
    return sorted[i] + frac * (sorted[Math.Min(i + 1, sorted.Length - 1)] - sorted[i]);
}

測試結果與 Excel、NumPy 一致,成功。


Comments

Be the first to post a comment

Post a comment