对所有数值从小到大排序,50% 位置就是 P50,P90 位置就是 P90,P95 位置就是 P95,P99 位置就是 P99。
但是今天同事问我为什么 P99 在 Excel 中算下来不等于其任何一个数值。
我看了一下,是采用的 Excel percentile.inc(A:A, 0.99) 来计算的。
询问 DeepSeek 之后才发现还有一点差别:
| 计算方式 | 核心思路 | 取值结果 |
|---|---|---|
| "主流"或"向上取整"法 | 位置 = CEIL(0.99 × n) | 第 n 位的值 |
| "邻近平均值"法 | 位置 = 1 + (n - 1) × 0.99 | n 左右两位的加权平均 |
| SPSS/ SAS 等软件常用方法 | 位置 = (n + 1) × 0.99 | n 左右两位的加权平均 |
Excel 采用的就是“邻近平均值”方式计算。
位置 = 1 + (n-1)*k
值 = N[a] + (N[a+1] - N[a]) * r
| 行数 | 值 |
|---|---|
| 109 | 3.437 |
| 110 | 4.268 |
| 111 | 4.274 |
111 * 0.99 = 109.89,按照常理,P99 应该等于 110 行的值
但是 Excel 中是这样算:
1 + (111 - 1) × 0.99 = 109.9
第 109 位:3.437
第 110 位:4.268
3.437 * (1-0.9) + 4.268 * 0.9 = 4.1849
用 Python 实现
import math
def percentile(data, p):
if not data:
raise ValueError("数据列表不能为空")
if p < 0 or p > 1:
raise ValueError("参数 p 必须在 0.0 到 1.0 之间")
# 处理可能的NaN或None(简单示例:过滤掉None和NaN)
clean_data = []
for x in data:
if x is None:
continue
if isinstance(x, float) and math.isnan(x):
continue
clean_data.append(x)
if not clean_data:
raise ValueError("数据列表中不包含有效数值")
sorted_data = sorted(clean_data)
n = len(sorted_data)
# 处理边界
if p == 0:
return sorted_data[0]
if p == 1:
return sorted_data[-1]
# 计算位置并进行线性插值
pos = (n -1) * p
k = int(pos)
f = pos - k
return (1 - f) * sorted_data[k] + f * sorted_data[k + 1]
data = [0] * 108 + [3.437, 4.268, 4.274]
p99 = percentile(data, 0.99)
print(f"第90百分位数: {p99:.6f}")