未记录事实核验 · 状态:现行内容
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适用于分位数计算、插值比较、可视化、经验风险指标、滚动分位数及分位数回归的教学练习;房价、股价、身高和网站性能数据均为示例或模拟数据,不构成现实结论。部分代码块沿用前文的导入或变量,单独运行时需补齐相应上下文。有限样本的阈值占比受插值约定、重复值和显示取整影响,不能直接等同于名义百分比;手动函数适用于非空数值数据且q在0至1之间。风险示例采用损失率口径的经验VaR及尾部样本均值估计,不保证未来损失概率;分位数回归对响应变量方向的异常值通常更稳健,但不保证抵抗高杠杆点。本次仅作结构审阅,未执行代码或开展外部事实与依赖版本兼容性核验。
📏 分位数(Quantiles)
abstract 📋 本节概览
- 🎯 学习目标:理解分位数的定义、计算方法和实际应用
- 📚 前置知识:描述性统计|描述性统计 | 中位数|中位数
- ⏱️ 预计用时:10分钟
🤔 什么是分位数?
note 📖 **分位数的定义**
分位数用于描述有序数据或概率分布中的位置。第p百分位数对应比例p/100的位置;有限样本的计算结果取决于所用的分位数定义和插值方法,有重复值时也不能保证恰有p%的样本小于等于该值。
生活例子:
- 高考成绩的"一本线"可以理解为某个分位数,比如前20%考生的分数线
- 身高的"第75百分位数"描述身高分布中75%位置附近的阈值;有限样本或有重复值时,低于该值的人数占比不一定恰为75%
- 收入的"第90百分位数"描述收入分布中90%位置附近的阈值;高于该值的人数占比不一定恰为10%
📊 常用分位数类型
1. 百分位数(Percentiles)
将数据分为100等份
import numpy as np
import matplotlib.pyplot as plt
# 模拟身高数据
np.random.seed(42)
heights = np.random.normal(170, 10, 1000)
# 计算常用百分位数
percentiles = [10, 25, 50, 75, 90, 95, 99]
for p in percentiles:
value = np.percentile(heights, p)
print(f"第{p}百分位数: {value:.2f}cm")
print(f"\n解释:第75百分位数 = {np.percentile(heights, 75):.2f}cm")
print("这是模拟身高样本中75%位置附近的阈值;实际低于或高于该值的样本占比受插值和重复值影响,不一定恰为75%和25%")
2. 四分位数(Quartiles)
将数据分为4等份
data = [1, 3, 4, 6, 8, 9, 11, 14, 15, 17, 20, 22, 25]
Q1 = np.percentile(data, 25) # 第一四分位数
Q2 = np.percentile(data, 50) # 第二四分位数(中位数)
Q3 = np.percentile(data, 75) # 第三四分位数
print(f"Q1 (第一四分位数): {Q1}")
print(f"Q2 (第二四分位数/中位数): {Q2}")
print(f"Q3 (第三四分位数): {Q3}")
print(f"四分位距 IQR = Q3 - Q1 = {Q3 - Q1}")
# 数据分布情况
print(f"\n数据分布:")
for p, value in [(25, Q1), (50, Q2), (75, Q3)]:
proportion = sum(x <= value for x in data) / len(data)
print(f"第{p}百分位数为{value},实际小于等于该值的样本占比为{proportion:.2%}")
3. 十分位数(Deciles)
将数据分为10等份
# 学生成绩数据
scores = np.random.normal(75, 15, 500)
scores = np.clip(scores, 0, 100) # 限制在0-100范围
deciles = [np.percentile(scores, i*10) for i in range(1, 10)]
print("成绩十分位数:")
for i, d in enumerate(deciles, 1):
print(f"D{i} (第{i*10}百分位): {d:.1f}分")
# 成绩等级划分
print(f"\n基于十分位数的成绩等级:")
print(f"优秀(按第90百分位阈值划分): ≥{deciles[8]:.1f}分")
print(f"良好(第70至第90百分位区间): ≥{deciles[6]:.1f}且<{deciles[8]:.1f}分")
print(f"中等(第30至第70百分位区间): ≥{deciles[2]:.1f}且<{deciles[6]:.1f}分")
print("显示阈值已取整;实际分组应使用未取整阈值,重复分数可能使各组人数占比偏离名义比例")
💻 Python实现方法
方法1:使用NumPy
import numpy as np
data = [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
# 计算特定分位数
print("NumPy方法:")
print(f"第25百分位数: {np.percentile(data, 25)}")
print(f"第50百分位数: {np.percentile(data, 50)}")
print(f"第75百分位数: {np.percentile(data, 75)}")
# 计算多个分位数
quantiles = np.percentile(data, [25, 50, 75])
print(f"四分位数: {quantiles}")
# 使用quantile函数(0-1范围)
print(f"\n使用quantile函数:")
print(f"0.25分位数: {np.quantile(data, 0.25)}")
print(f"0.5分位数: {np.quantile(data, 0.5)}")
print(f"0.75分位数: {np.quantile(data, 0.75)}")
方法2:使用Pandas
import pandas as pd
# 创建Series
data_series = pd.Series([1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50])
print("Pandas方法:")
print(f"第25百分位数: {data_series.quantile(0.25)}")
print(f"第50百分位数: {data_series.quantile(0.50)}")
print(f"第75百分位数: {data_series.quantile(0.75)}")
# 计算多个分位数
quantiles = data_series.quantile([0.1, 0.25, 0.5, 0.75, 0.9])
print(f"\n多个分位数:\n{quantiles}")
# 描述性统计(包含分位数)
print(f"\n描述性统计:\n{data_series.describe()}")
方法3:手动实现
def calculate_quantile(data, q):
"""
手动计算分位数
q: 分位数比例(0-1之间)
"""
if not 0 <= q <= 1:
raise ValueError("q必须在0至1之间")
sorted_data = sorted(data)
n = len(sorted_data)
if n == 0:
raise ValueError("data不能为空")
# 使用线性插值方法,并兼容整数端点q=0或q=1
index = float((n - 1) * q)
if index.is_integer():
return sorted_data[int(index)]
else:
lower_index = int(index)
upper_index = lower_index + 1
weight = index - lower_index
return sorted_data[lower_index] * (1 - weight) + sorted_data[upper_index] * weight
# 测试手动实现
data = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
print("手动计算:")
for q in [0.25, 0.5, 0.75]:
result = calculate_quantile(data, q)
print(f"{q*100}%分位数: {result}")
# 与NumPy结果比较
numpy_result = np.quantile(data, q)
print(f"NumPy结果: {numpy_result}, 差值: {abs(result - numpy_result):.6f}")
print()
📈 分位数的应用场景
1. 异常值检测
def detect_outliers_iqr(data, k=1.5):
"""使用IQR方法检测异常值"""
Q1 = np.percentile(data, 25)
Q3 = np.percentile(data, 75)
IQR = Q3 - Q1
# 定义异常值边界
lower_bound = Q1 - k * IQR
upper_bound = Q3 + k * IQR
outliers = []
for x in data:
if x < lower_bound or x > upper_bound:
outliers.append(x)
return outliers, lower_bound, upper_bound
# 示例:检测工资数据中的异常值
salaries = [3000, 3200, 3500, 3800, 4000, 4200, 4500, 4800, 5000, 25000, 30000]
outliers, lower, upper = detect_outliers_iqr(salaries)
print(f"正常范围: [{lower:.0f}, {upper:.0f}]")
print(f"异常值: {outliers}")
print(f"Q1: {np.percentile(salaries, 25):.0f}")
print(f"Q3: {np.percentile(salaries, 75):.0f}")
2. 性能评估
# 模拟网页加载时间数据(毫秒)
np.random.seed(42)
load_times = np.random.lognormal(mean=2, sigma=0.5, size=10000)
# 关键性能指标
percentiles = [50, 90, 95, 99]
print("网页加载时间性能指标:")
for p in percentiles:
value = np.percentile(load_times, p)
print(f"P{p}: {value:.0f}ms")
print(f"\n性能解读:")
for p in [50, 90, 99]:
threshold = np.percentile(load_times, p)
proportion = np.mean(load_times <= threshold)
print(f"模拟样本P{p}约为{threshold:.2f}ms;按未取整阈值计算,实际小于等于该值的样本占比为{proportion:.2%}")
3. 风险管理
# 模拟股票收益率数据
np.random.seed(42)
returns = np.random.normal(0.08, 0.2, 1000) # 年化收益率
# 风险指标(VaR - Value at Risk)
confidence_levels = [0.95, 0.99]
print("投资风险分析 (VaR):")
for conf in confidence_levels:
return_quantile = np.percentile(returns, (1-conf)*100)
var = -return_quantile # 采用损失率=-收益率的符号约定
print(f"{conf*100}%置信水平下的经验VaR(损失率口径): {var:.2%}")
print(f"模拟样本中约有{(1-conf)*100}%的损失率超过该阈值;这不是未来损失概率的保证")
# 条件风险价值(CVaR)
return_quantile_05 = np.percentile(returns, 5)
cvar_95 = -np.mean(returns[returns <= return_quantile_05])
print(f"\n经验条件VaR (95%,损失率口径): {cvar_95:.2%}")
print(f"模拟样本下尾约5%的平均损失率为{cvar_95:.2%};此处采用尾部样本均值估计")
📊 可视化分位数
1. 箱线图展示分位数
import matplotlib.pyplot as plt
import seaborn as sns
# 生成不同分布的数据
np.random.seed(42)
normal_data = np.random.normal(50, 10, 1000)
skewed_data = np.random.exponential(2, 1000)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 正态分布箱线图
ax1.boxplot(normal_data)
ax1.set_title('正态分布数据')
ax1.set_ylabel('数值')
# 偏斜分布箱线图
ax2.boxplot(skewed_data)
ax2.set_title('指数分布数据(右偏)')
ax2.set_ylabel('数值')
plt.tight_layout()
plt.show()
# 打印分位数信息
print("正态分布数据的分位数:")
for p in [25, 50, 75]:
print(f"Q{p//25 if p != 50 else 2}: {np.percentile(normal_data, p):.2f}")
print("\n指数分布数据的分位数:")
for p in [25, 50, 75]:
print(f"Q{p//25 if p != 50 else 2}: {np.percentile(skewed_data, p):.2f}")
2. 分位数-分位数图(Q-Q图)
from scipy import stats
import matplotlib.pyplot as plt
# 生成数据
np.random.seed(42)
normal_data = np.random.normal(0, 1, 1000)
uniform_data = np.random.uniform(-2, 2, 1000)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 正态数据的Q-Q图
stats.probplot(normal_data, dist="norm", plot=ax1)
ax1.set_title('正态数据 vs 正态分布')
# 均匀数据的Q-Q图
stats.probplot(uniform_data, dist="norm", plot=ax2)
ax2.set_title('均匀数据 vs 正态分布')
plt.tight_layout()
plt.show()
🔍 分位数的计算方法
不同插值方法比较
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# NumPy的不同插值方法
methods = ['linear', 'lower', 'higher', 'midpoint', 'nearest']
q = 0.25 # 第25百分位数
print(f"数据: {data}")
print(f"第{q*100}%分位数的不同计算方法:")
for method in methods:
result = np.quantile(data, q, interpolation=method)
print(f"{method:>10}: {result}")
# 可视化不同方法的差异
import matplotlib.pyplot as plt
# 对于不同的分位数值,比较各种方法
quantiles = np.arange(0.1, 1.0, 0.1)
methods_data = {}
for method in methods:
methods_data[method] = [np.quantile(data, q, interpolation=method) for q in quantiles]
plt.figure(figsize=(10, 6))
for method, values in methods_data.items():
plt.plot(quantiles, values, marker='o', label=method)
plt.xlabel('分位数')
plt.ylabel('分位数值')
plt.title('不同插值方法的分位数计算结果')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
🛠️ 实用技巧和最佳实践
1. 分组分位数分析
import pandas as pd
# 创建示例数据:不同城市的房价
np.random.seed(42)
cities = ['北京', '上海', '深圳', '广州'] * 250
prices = []
base_prices = {'北京': 60000, '上海': 55000, '深圳': 50000, '广州': 30000}
for city in cities:
price = np.random.normal(base_prices[city], base_prices[city] * 0.3)
prices.append(max(price, base_prices[city] * 0.5)) # 确保价格不会过低
df = pd.DataFrame({'城市': cities, '房价': prices})
# 计算各城市房价分位数
print("各城市房价分位数分析:")
for city in df['城市'].unique():
city_data = df[df['城市'] == city]['房价']
q25, q50, q75 = city_data.quantile([0.25, 0.5, 0.75])
print(f"\n{city}:")
print(f" Q1: {q25:,.0f}元/㎡")
print(f" 中位数: {q50:,.0f}元/㎡")
print(f" Q3: {q75:,.0f}元/㎡")
print(f" IQR: {q75-q25:,.0f}元/㎡")
2. 动态分位数追踪
# 模拟股价数据
dates = pd.date_range('2023-01-01', periods=252, freq='D')
np.random.seed(42)
price_changes = np.random.normal(0.001, 0.02, 252)
prices = [100] # 初始价格
for change in price_changes:
prices.append(prices[-1] * (1 + change))
df_stock = pd.DataFrame({'日期': dates, '价格': prices[1:]})
# 计算滚动分位数
window = 30 # 30天窗口
df_stock['Q25'] = df_stock['价格'].rolling(window).quantile(0.25)
df_stock['Q50'] = df_stock['价格'].rolling(window).quantile(0.50)
df_stock['Q75'] = df_stock['价格'].rolling(window).quantile(0.75)
# 可视化
plt.figure(figsize=(12, 6))
plt.plot(df_stock['日期'], df_stock['价格'], label='股价', alpha=0.7)
plt.plot(df_stock['日期'], df_stock['Q25'], label='Q1 (25%)', linestyle='--')
plt.plot(df_stock['日期'], df_stock['Q50'], label='中位数', linestyle='-')
plt.plot(df_stock['日期'], df_stock['Q75'], label='Q3 (75%)', linestyle='--')
plt.title(f'{window}天滚动分位数追踪')
plt.xlabel('日期')
plt.ylabel('价格')
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
3. 分位数回归
from sklearn.linear_model import QuantileRegressor
import numpy as np
import matplotlib.pyplot as plt
# 生成带有异方差的数据
np.random.seed(42)
n = 200
x = np.random.uniform(0, 10, n)
# 噪声随x增大而增大(异方差)
noise = np.random.normal(0, 0.5 + 0.3 * x)
y = 2 * x + 3 + noise
# 普通最小二乘回归
from sklearn.linear_model import LinearRegression
ols = LinearRegression()
ols.fit(x.reshape(-1, 1), y)
# 分位数回归
quantiles = [0.1, 0.5, 0.9]
quantile_regressors = {}
x_plot = np.linspace(0, 10, 100).reshape(-1, 1)
plt.figure(figsize=(10, 6))
plt.scatter(x, y, alpha=0.5, label='数据点')
# OLS回归线
y_ols = ols.predict(x_plot)
plt.plot(x_plot, y_ols, 'r-', label='OLS回归', linewidth=2)
# 分位数回归线
colors = ['blue', 'green', 'orange']
for i, q in enumerate(quantiles):
qr = QuantileRegressor(quantile=q, alpha=0)
qr.fit(x.reshape(-1, 1), y)
y_pred = qr.predict(x_plot)
plt.plot(x_plot, y_pred, color=colors[i], linestyle='--',
label=f'{q*100}%分位数回归', linewidth=2)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('分位数回归 vs 普通最小二乘回归')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
print("分位数回归的优势:")
print("1. 能够描述条件分布的不同部分")
print("2. 相比最小二乘回归,对响应变量方向的异常值通常更稳健,但不保证抵抗解释变量中的高杠杆点")
print("3. 适合处理异方差数据")
print("4. 提供更全面的关系描述")
🔗 相关知识
- 中位数
- 描述性统计
- 共同能力基础/SQL基本概念
- 异常值检测
- 箱线图
- 集中趋势指标
#统计学 #分位数 #描述性统计 #位置指标 #基础