在 Python 中计算累积分布函数 (CDF)

Posted

技术标签:

【中文标题】在 Python 中计算累积分布函数 (CDF)【英文标题】:Calculate the Cumulative Distribution Function (CDF) in Python 【发布时间】:2014-09-07 10:03:48 【问题描述】:

如何在 python 中计算Cumulative Distribution Function (CDF)?

我想从我拥有的点数组(离散分布)中计算它,而不是像 scipy 那样使用连续分布。

【问题讨论】:

numpy.cumsum怎么样? 要使用numpy.cumsum相信你首先需要计算PDF,这是一个开销。 您正在寻找ECDF。 @DrV 为您提供了一个简单的版本。它也可以在statsmodels 中找到。 【参考方案1】:

(我对问题的解释可能是错误的。如果问题是如何从离散 PDF 转换为离散 CDF,那么如果样本是等间距的,那么 np.cumsum 除以一个合适的常数就可以了。如果数组不是等间距的,然后数组的np.cumsum 乘以点之间的距离即可。)

如果您有一个离散的样本数组,并且您想知道样本的 CDF,那么您可以对数组进行排序。如果您查看排序结果,您会发现最小值代表 0% ,最大值代表 100 %。如果您想知道分布的 50 % 处的值,只需查看已排序数组中间的数组元素即可。

让我们通过一个简单的例子来仔细看看:

import matplotlib.pyplot as plt
import numpy as np

# create some randomly ddistributed data:
data = np.random.randn(10000)

# sort the data:
data_sorted = np.sort(data)

# calculate the proportional values of samples
p = 1. * np.arange(len(data)) / (len(data) - 1)

# plot the sorted data:
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.plot(p, data_sorted)
ax1.set_xlabel('$p$')
ax1.set_ylabel('$x$')

ax2 = fig.add_subplot(122)
ax2.plot(data_sorted, p)
ax2.set_xlabel('$x$')
ax2.set_ylabel('$p$')

这给出了下图,其中右侧的图是传统的累积分布函数。它应该反映点后面过程的CDF,但自然不是只要点的数量是有限的。

这个函数很容易反转,取决于你的应用你需要哪种形式。

【讨论】:

整洁!谢谢你的回答。我不知道我是否应该为此创建一个新问题,但是,如果我的数据有 N 维怎么办? (出于示例的目的,让我们说 2) 如何获得可以使用的功能?你的答案只是情节。 这个np.linspace(0, 1, len(data))1. * arange(len(data)) / (len(data) - 1) 干净 @Tjorriemorrie 要将此作为实际函数,您可以使用插值:f = lambda x: np.interp(x, p, data_sorted)。然后可以f(0.5) 为例,获取中位数。 @charmoniumQ:我也更喜欢linspace,但值得一提的是使用np.linspace(0, 1, len(data), endpoint=False)np.arange(len(data)) / len(data) 是一种很好的做法,否则它不是CDF 的无偏估计量。我喜欢this post 有详细的解释。【参考方案2】:

假设您知道数据的分布方式(即您知道数据的 pdf),那么 scipy 在计算 cdf 时确实支持离散数据

import numpy as np
import scipy
import matplotlib.pyplot as plt
import seaborn as sns

x = np.random.randn(10000) # generate samples from normal distribution (discrete data)
norm_cdf = scipy.stats.norm.cdf(x) # calculate the cdf - also discrete

# plot the cdf
sns.lineplot(x=x, y=norm_cdf)
plt.show()

我们甚至可以打印 cdf 的前几个值以显示它们是离散的

print(norm_cdf[:10])
>>> array([0.39216484, 0.09554546, 0.71268696, 0.5007396 , 0.76484329,
       0.37920836, 0.86010018, 0.9191937 , 0.46374527, 0.4576634 ])

计算cdf的相同方法也适用于多个维度:我们使用下面的二维数据来说明

mu = np.zeros(2) # mean vector
cov = np.array([[1,0.6],[0.6,1]]) # covariance matrix
# generate 2d normally distributed samples using 0 mean and the covariance matrix above
x = np.random.multivariate_normal(mean=mu, cov=cov, size=1000) # 1000 samples
norm_cdf = scipy.stats.norm.cdf(x)
print(norm_cdf.shape)
>>> (1000, 2)

在上面的例子中,我事先知道我的数据是正态分布的,这就是我使用scipy.stats.norm() 的原因——scipy 支持多种分布。但同样,您需要事先知道您的数据是如何分布的才能使用这些功能。如果您不知道您的数据是如何分布的,而只是使用任何分布来计算 cdf,那么您很可能会得到不正确的结果。

【讨论】:

我不明白从正态分布中采样向量x 的意义。向量x 而不是linespace 来绘制您从scipy.stats 使用的cdf 的参数版本。无论如何,OP要求非参数CDF,他要求离散但他很可能是指ECDF,这是非参数的。【参考方案3】:

经验累积分布函数是一个 CDF,它会在您的数据集中的值处精确跳跃。 离散分布的 CDF 会在您的每个值处放置一个质量,其中质量与值的频率成正比。由于质量总和必须为 1,因此这些约束决定了经验 CDF 中每次跳跃的位置和高度。

给定一个值数组a,您可以通过首先获取值的频率来计算经验 CDF。 numpy 函数unique() 在这里很有帮助,因为它不仅返回频率,还返回排序顺序的值。要计算累积分布,请使用cumsum() 函数,然后除以总和。以下函数按排序顺序返回值和相应的累积分布:

import numpy as np

def ecdf(a):
    x, counts = np.unique(a, return_counts=True)
    cusum = np.cumsum(counts)
    return x, cusum / cusum[-1]

要绘制经验 CDF,您可以使用 matplotlibplot() 函数。选项drawstyle='steps-post' 确保跳转发生在正确的位置。但是,您需要在最小的数据值处强制跳转,因此需要在xy 前面插入一个额外的元素。

import matplotlib.pyplot as plt

def plot_ecdf(a):
    x, y = ecdf(a)
    x = np.insert(x, 0, x[0])
    y = np.insert(y, 0, 0.)
    plt.plot(x, y, drawstyle='steps-post')
    plt.grid(True)
    plt.savefig('ecdf.png')

示例用法:

xvec = np.array([7,1,2,2,7,4,4,4,5.5,7])
plot_ecdf(xvec)

df = pd.DataFrame('x':[7,1,2,2,7,4,4,4,5.5,7])
plot_ecdf(df['x'])

带输出:

【讨论】:

以上是关于在 Python 中计算累积分布函数 (CDF)的主要内容,如果未能解决你的问题,请参考以下文章

如何计算累积正态分布?

matlab使用cdfplot画累积分布函数CDF图像

概率密度函数(PDF)和累积概率密度函数(CDF)

在 R 中绘制数据集的 CDF?

mean() 函数可以显示累积分布函数的概率吗?

逆CDF变换采样的分布略有错误