如何针对通用函数y(x)计算百分位数对应的x值及各类统计量(优先采用np.percentile)
解决方法:从函数y(x)计算统计量与百分位数
这个问题核心是把函数形式的分布转换成能计算统计量的形式——毕竟np.percentile这类工具都是给样本数据用的,我来一步步给你捋清楚怎么搞:
一、先明确y(x)的角色
首先得确认你的y(x)是x的概率密度函数(PDF):也就是对于任意区间[a,b],x落在这个区间的概率等于∫ₐᵇ y(x)dx。如果y(x)的积分(从负无穷到正无穷)不等于1,那第一步必须归一化,因为所有统计量都是基于概率分布(总概率为1)计算的:
import numpy as np from scipy.integrate import quad # 先计算y(x)的总积分 total_integral, _ = quad(y, -np.inf, np.inf) # 归一化后的PDF,确保总概率为1 def normalized_pdf(x): return y(x) / total_integral
如果你的y(x)本来就是归一化的(积分等于1),这一步直接跳过就行。
二、计算各类统计量
1. 均值(期望)
均值的定义是E[x] = ∫₋∞^∞ x * normalized_pdf(x) dx,用数值积分就能直接算:
mean, _ = quad(lambda x: x * normalized_pdf(x), -np.inf, np.inf)
2. 方差与标准差
方差是Var[x] = E[x²] - (E[x])²,所以先算x²的期望,再代入公式:
x_squared_expectation, _ = quad(lambda x: x**2 * normalized_pdf(x), -np.inf, np.inf) variance = x_squared_expectation - mean**2 std_dev = np.sqrt(variance)
3. 百分位数(包括中位数)
百分位数的定义是:对于p%分位数x_p,满足∫₋∞^x_p normalized_pdf(x) dx = p/100。这里有两种常用方法,你可以根据情况选:
方法A:生成样本后用np.percentile
这是最直观的方法——先生成服从该分布的样本,然后直接用np.percentile计算。通用的样本生成方法是拒绝采样,代码如下:
def rejection_sampling(pdf, x_min, x_max, sample_size): # 先找到PDF在[x_min, x_max]范围内的最大值,用来确定采样上限 x_grid = np.linspace(x_min, x_max, 1000) pdf_max = np.max(pdf(x_grid)) samples = [] while len(samples) < sample_size: # 生成均匀分布的x和随机阈值u x = np.random.uniform(x_min, x_max) u = np.random.uniform(0, pdf_max) if u <= pdf(x): samples.append(x) return np.array(samples) # 假设你的x取值范围是[-10,10],可以根据实际情况调整 samples = rejection_sampling(normalized_pdf, x_min=-10, x_max=10, sample_size=100000) # 现在就可以用np.percentile了! median = np.percentile(samples, 50) # 中位数(50%分位数) p95 = np.percentile(samples, 95) # 95%分位数
样本量越大结果越准,一般10万到100万样本就足够了。
方法B:直接数值求解CDF的逆
另一种方法是先定义累积分布函数(CDF),然后求解CDF等于目标百分位的x值:
from scipy.optimize import root_scalar # 定义CDF:P(X ≤ x) = 从负无穷到x的PDF积分 def cdf(x): integral, _ = quad(normalized_pdf, -np.inf, x) return integral # 找p分位数(p是0到1之间的数,比如0.5对应中位数) def find_percentile(p, x_min, x_max, x_guess): def equation(x): return cdf(x) - p # 用brentq方法求解根,需要指定x的范围 result = root_scalar(equation, bracket=[x_min, x_max], method='brentq') return result.root # 计算中位数 median = find_percentile(0.5, x_min=-10, x_max=10, x_guess=0) # 计算95%分位数 p95 = find_percentile(0.95, x_min=-10, x_max=10, x_guess=5)
这种方法不需要生成大量样本,适合PDF形态特殊(比如很窄、很陡)的情况,结果也更精确。
三、特殊情况:如果y(x)是累积分布函数(CDF)
如果你的y(x)本来就是CDF(即y(x) = P(X ≤ x)),那分位数直接求逆函数就行:
from scipy.optimize import root_scalar def find_percentile_from_cdf(p, cdf, x_min, x_max, x_guess): def equation(x): return cdf(x) - p result = root_scalar(equation, bracket=[x_min, x_max], method='brentq') return result.root median = find_percentile_from_cdf(0.5, y, x_min=-10, x_max=10, x_guess=0)
至于均值和标准差,需要先把CDF转换成PDF(CDF的导数),再用前面的积分方法计算:
from scipy.misc import derivative def pdf_from_cdf(x): return derivative(y, x, dx=1e-6)
内容的提问来源于stack exchange,提问作者maelstromscientist




