如何绘制经验 cdf (ecdf)

Posted

技术标签:

【中文标题】如何绘制经验 cdf (ecdf)【英文标题】:How to plot empirical cdf (ecdf) 【发布时间】:2011-03-13 16:02:34 【问题描述】:

如何在 Python 中绘制 matplotlib 中数字数组的经验 CDF?我正在寻找 pylab 的“hist”函数的 cdf 模拟。

我能想到的一件事是:

from scipy.stats import cumfreq
a = array([...]) # my array of numbers
num_bins =  20
b = cumfreq(a, num_bins)
plt.plot(b)

【问题讨论】:

【参考方案1】:

这看起来(几乎)正是您想要的。两件事:

首先,结果是一个包含四个项目的元组。第三是垃圾箱的大小。第二个是最小 bin 的起点。第一个是每个 bin 中或下方的点数。 (最后一个是超出限制的点数,但由于您没有设置任何点,所有点都会被分箱。)

其次,您需要重新调整结果,使最终值为 1,以遵循 CDF 的通常约定,否则它是正确的。

这是它的底层功能:

def cumfreq(a, numbins=10, defaultreallimits=None):
    # docstring omitted
    h,l,b,e = histogram(a,numbins,defaultreallimits)
    cumhist = np.cumsum(h*1, axis=0)
    return cumhist,l,b,e

它进行直方图,然后生成每个 bin 中计数的累积总和。所以结果的第 i 个值是小于或等于第 i 个 bin 的最大值的数组值的个数。所以,最终的值就是初始数组的大小。

最后,要绘制它,您需要使用 bin 的初始值和 bin 大小来确定您需要的 x 轴值。

另一个选项是使用numpy.histogram,它可以进行标准化并返回 bin 边缘。您需要自己计算结果计数的累积总和。

a = array([...]) # your array of numbers
num_bins = 20
counts, bin_edges = numpy.histogram(a, bins=num_bins, normed=True)
cdf = numpy.cumsum(counts)
pylab.plot(bin_edges[1:], cdf)

bin_edges[1:] 是每个 bin 的上边缘。)

【讨论】:

请注意:此代码实际上并没有为您提供经验 CDF(在每个 n 个数据点处增加 1/n 的阶跃函数)。相反,此代码根据基于直方图的 PDF 估计值给出 CDF 估计值。这种基于直方图的估计可以通过仔细/不正确地选择 bin 来操纵/偏差,因此它不如实际 ECDF 那样对真正的 CDF 进行表征。 我也不喜欢这强加分箱的观点;请参阅 Dave 的简短回答,它只是使用 numpy.sort 绘制 CDF 而不进行分箱。【参考方案2】:

您想用 CDF 做什么? 绘制它,这是一个开始。您可以尝试一些不同的值,如下所示:

from __future__ import division
import numpy as np
from scipy.stats import cumfreq
import pylab as plt

hi = 100.
a = np.arange(hi) ** 2
for nbins in ( 2, 20, 100 ):
    cf = cumfreq(a, nbins)  # bin values, lowerlimit, binsize, extrapoints
    w = hi / nbins
    x = np.linspace( w/2, hi - w/2, nbins )  # care
    # print x, cf
    plt.plot( x, cf[0], label=str(nbins) )

plt.legend()
plt.show()

Histogram 列出了箱数的各种规则,例如num_bins ~ sqrt( len(a) ).

(细则:这里发生了两件完全不同的事情,

对原始数据进行分箱/直方图 plot 通过 20 个分箱值插入一条平滑曲线。

这两种方法中的任何一种都可能对“成块”的数据产生影响 或者有长尾,即使对于 1d 数据也是如此 - 2d、3d 数据变得越来越困难。 也可以看看 Density_estimation 和 using scipy gaussian kernel density estimation )。

【讨论】:

【参考方案3】:

您可以使用scikits.statsmodels 库中的ECDF 函数:

import numpy as np
import scikits.statsmodels as sm
import matplotlib.pyplot as plt

sample = np.random.uniform(0, 1, 50)
ecdf = sm.tools.ECDF(sample)

x = np.linspace(min(sample), max(sample))
y = ecdf(x)
plt.step(x, y)

在 0.4 版中,scicits.statsmodels 已重命名为 statsmodelsECDF 现在位于 distributions 模块中(而 statsmodels.tools.tools.ECDF 已弃用)。

import numpy as np
import statsmodels.api as sm # recommended import according to the docs
import matplotlib.pyplot as plt

sample = np.random.uniform(0, 1, 50)
ecdf = sm.distributions.ECDF(sample)

x = np.linspace(min(sample), max(sample))
y = ecdf(x)
plt.step(x, y)
plt.show()

【讨论】:

@bmu(和@Luca):太棒了;感谢您使用当前的 statsmodel 使代码成为最新! 对于 scikits.statsmodels v0.3.1 必须 import scikits.statsmodels.tools as smtoolsecdf = smtools.tools.EDCF(...) 这仍然会通过x = np.linspace(…) 进行分箱。您可以使用plt.step(ecdf.x,ecdf.y) 绕过此问题。 在 statsmodels v12.2 中,您可以从 from statsmodels.distributions.empirical_distribution import ECDF (statsmodels.org/stable/generated/…) 获取 ECDF【参考方案4】:

我对 AFoglia 的方法有一个简单的补充,用于标准化 CDF

n_counts,bin_edges = np.histogram(myarray,bins=11,normed=True) 
cdf = np.cumsum(n_counts)  # cdf not normalized, despite above
scale = 1.0/cdf[-1]
ncdf = scale * cdf

规范化 histo 使其 integral 统一,这意味着 cdf 不会被规范化。你必须自己扩展它。

【讨论】:

【参考方案5】:

您是否尝试过 pyplot.hist 的累积=True 参数?

【讨论】:

非常好的评论。尽管如此,这还是强加了分箱;使用 np.sort 查看 Dave 的回答。 不错且简单的选项,但缺点是对结果线图的定制有限,例如不知道如何添加标记。去scikits.statsmodels回答。【参考方案6】:

如果你喜欢linspace 并且更喜欢单行,你可以这样做:

plt.plot(np.sort(a), np.linspace(0, 1, len(a), endpoint=False))

鉴于我的口味,我几乎总是这样做:

# a is the data array
x = np.sort(a)
y = np.arange(len(x))/float(len(x))
plt.plot(x, y)

即使有 >O(1e6) 数据值,这也对我有用。 如果你真的需要下采样,我会设置

x = np.sort(a)[::down_sampling_step]

编辑回复评论/编辑我为什么使用上面定义的endpoint=Falsey。以下是一些技术细节。

经验 CDF 通常正式定义为

CDF(x) = "number of samples <= x"/"number of samples"

为了完全匹配这个正式的定义,您需要使用 y = np.arange(1,len(x)+1)/float(len(x)) 以便我们得到 y = [1/N, 2/N ... 1]。这个估计器是一个无偏估计器,它将在无限样本Wikipedia ref. 的限制下收敛到真正的 CDF。

我倾向于使用y = [0, 1/N, 2/N ... (N-1)/N],因为(a)它更容易编码/更惯用,(b)但仍然是正式的,因为在收敛证明中总是可以将CDF(x)1-CDF(x)交换,并且( c) 使用上述(简单的)下采样方法。

在某些特殊情况下,定义是有用的

y = (arange(len(x))+0.5)/len(x)

介于这两种约定之间。实际上,它表示“1/(2N) 的值可能小于我在样本中看到的最低值,1/(2N) 的值可能大于我迄今为止看到的最大值.

请注意,此约定的选择会与 plt.step 中使用的 where 参数交互,如果它看起来更有用的话 CDF 作为分段常数函数。为了完全匹配上面提到的正式定义,需要使用where=pre 建议的y=[0,1/N..., 1-1/N] 约定,或where=posty=[1/N, 2/N ... 1] 约定,但不能反过来。

但是,对于大样本和合理分布,答案主体中给出的约定易于编写,是真实 CDF 的无偏估计量,并且适用于下采样方法。

【讨论】:

这个答案应该会得到更多的支持,因为它是迄今为止唯一一个不强制分箱的答案。我只是稍微简化了代码,使用 linspace。 @hans_meine 您的编辑,即yvals=linspace(0,1,len(sorted)),产生的yvals 不是真正CDF 的无偏估计量。 那么,我们应该用 linspace 和 endpoint = False,对吧? @Dave 使用 plt.step 代替 plt.plot 会更好吗?如果这样做有什么问题吗? @EzequielCastaño 大多数情况下,我认为这是一种风格,但您需要注意与y 参数的定义相关的where 参数的选择。对我来说最有意义的是使用where=pre 建议的y=np.arange(0,len(x))/len(x),或者你可以使用y=np.arange(1,len(x)+1)/len(x) 并使用where=post,但是在它们之间切换“位置”会(非常轻微)歪曲 CDF。【参考方案7】:

如果您想显示实际的真实 ECDF(正如 David B 所指出的,它是一个阶跃函数,在 n 个数据点中的每一个处增加 1/n),我的建议是编写代码为每个数据点生成两个“绘图”点:

a = array([...]) # your array of numbers
sorted=np.sort(a)
x2 = []
y2 = []
y = 0
for x in sorted: 
    x2.extend([x,x])
    y2.append(y)
    y += 1.0 / len(a)
    y2.append(y)
plt.plot(x2,y2)

通过这种方式,您将获得一个包含 n 个步骤的图,这些步骤是 ECDF 的特征,这对于小到足以让步骤可见的数据集尤其有用。此外,无需对直方图进行任何分箱(这可能会给绘制的 ECDF 带来偏差)。

【讨论】:

【参考方案8】:

我们可以只使用matplotlib 中的step 函数,它会绘制逐步图,这就是经验CDF 的定义:

import numpy as np
from matplotlib import pyplot as plt

data = np.random.randn(11)

levels = np.linspace(0, 1, len(data) + 1)  # endpoint 1 is included by default
plt.step(sorted(list(data) + [max(data)]), levels)

max(data) 处的最后一条垂直线是手动添加的。否则,情节只会停在1 - 1/len(data) 级别。

或者,我们可以使用where='post' 选项到step()

levels = np.linspace(1. / len(data), 1, len(data))
plt.step(sorted(data), levels, where='post')

在这种情况下,不会绘制从零开始的初始垂直线。

【讨论】:

【参考方案9】:

(这是我对问题的回答的副本:Plotting CDF of a pandas series in python)

CDF 或累积分布函数图基本上是一个图表,其中 X 轴为排序值,Y 轴为累积分布。因此,我将创建一个新系列,其中排序值作为索引,累积分布作为值。

首先创建一个示例系列:

import pandas as pd
import numpy as np
ser = pd.Series(np.random.normal(size=100))

对系列进行排序:

ser = ser.order()

现在,在继续之前,再次附加最后一个(也是最大的)值。这一步很重要,尤其是对于小样本量以获得无偏 CDF:

ser[len(ser)] = ser.iloc[-1]

创建一个以排序值作为索引、累积分布作为值的新系列

cum_dist = np.linspace(0.,1.,len(ser))
ser_cdf = pd.Series(cum_dist, index=ser)

最后,将函数绘制成步骤:

ser_cdf.plot(drawstyle='steps')

【讨论】:

【参考方案10】:

基于戴夫的回答的单线:

plt.plot(np.sort(arr), np.linspace(0, 1, len(arr), endpoint=False))

编辑:hans_meine 在 cmets 中也建议这样做。

【讨论】:

【参考方案11】:

这是使用散景

from bokeh.plotting import figure, show
from statsmodels.distributions.empirical_distribution import ECDF
ecdf = ECDF(pd_series)
p = figure(title="tests", tools="save", background_fill_color="#E8DDCB")
p.line(ecdf.x,ecdf.y)
show(p)

【讨论】:

【参考方案12】:

假设 vals 包含您的值,那么您可以简单地绘制 CDF,如下所示:

y = numpy.arange(0, 101)
x = numpy.percentile(vals, y)
plot(x, y)

要在 0 和 1 之间缩放,只需将 y 除以 100。

【讨论】:

【参考方案13】:

它是 seaborn 中使用累积 = True 参数的单线。给你,

import seaborn as sns
sns.kdeplot(a, cumulative=True)

【讨论】:

【参考方案14】:

到目前为止,没有一个答案能涵盖我到达这里时想要的,即:

def empirical_cdf(x, data):
    "evaluate ecdf of data at points x"
    return np.mean(data[None, :] <= x[:, None], axis=1)

它在点 x 的数组处评估给定数据集的经验 CDF,这些点不必排序。没有中间分箱,也没有外部库。

对大 x 进行更好扩展的等效方法是对数据进行排序并使用 np.searchsorted:

def empirical_cdf(x, data):
    "evaluate ecdf of data at points x"
    data = np.sort(data)
    return np.searchsorted(data, x)/float(data.size)

【讨论】:

【参考方案15】:

在我看来,以前的方法都没有完成绘制经验 CDF 的完整(和严格)工作,这是提问者的原始问题。我为任何迷失和同情的灵魂发布我的建议。

我的建议有以下几点:1) 它考虑在第一个表达式 here 中定义的经验 CDF,即,就像在 AW Van der Waart 的 渐近统计 (1998) 中一样,2) 它显式显示函数的阶跃行为,3) 通过显示标记以解决不连续性,显式显示经验 CDF 从右侧连续,4) 将极值处的零值和一值扩展到用户定义的边距。我希望它可以帮助某人:

def plot_cdf( data, xaxis = None, figsize = (20,10), line_style = 'b-',
ball_style = 'bo', xlabel = r"Random variable $X$", ylabel = "$N$-samples
empirical CDF $F_X,N(x)$" ):
     # Contribution of each data point to the empirical distribution
     weights = 1/data.size * np.ones_like( data )
     # CDF estimation
     cdf = np.cumsum( weights )
     # Plot central part of the CDF
     plt.figure( figsize = (20,10) )
     plt.step( np.sort( a ), cdf, line_style, where = 'post' )
     # Plot valid points at discontinuities
     plt.plot( np.sort( a ), cdf, ball_style )
     # Extract plot axis and extend outside the data range
     if not xaxis == None:
         (xmin, xmax, ymin, ymax) = plt.axis( )
         xmin = xaxis[0]
         xmax = xaxis[1]
         plt.axis( [xmin, xmax, ymin, ymax] )
     else:
         (xmin,xmax,_,_) = plt.axis()
         plt.plot( [xmin, a.min(), a.min()], np.zeros( 3 ), line_style )
     plt.plot( [a.max(), xmax], np.ones( 2 ), line_style )
     plt.xlabel( xlabel )
     plt.ylabel( ylabel )

【讨论】:

【参考方案16】:

我为大型数据集评估 cdf 所做的工作 -

    找出唯一值

    unique_values = np.sort(pd.Series)

    为数据集中这些已排序且唯一的值创建排名数组 -

    ranks = np.arange(0,len(unique_values))/(len(unique_values)-1)

    绘制 unique_values 与排名

示例 下面的代码绘制了来自 kaggle 的人口 dataset 的 cdf -

us_census_data = pd.read_csv('acs2015_census_tract_data.csv')

population = us_census_data['TotalPop'].dropna()

## sort the unique values using pandas unique function
unique_pop = np.sort(population.unique())
cdf = np.arange(0,len(unique_pop),step=1)/(len(unique_pop)-1)

## plotting    
plt.plot(unique_pop,cdf)
plt.show()

【讨论】:

【参考方案17】: 这可以使用seaborn.ecdfplot 来完成 seabornmatplotlib 的高级 API data 可以是 pandas.DataFramenumpy.ndarraymappingsequence 请参阅How to use markers with ECDF plot 了解其他选项。
import seaborn as sns
import matplotlib.pyplot as plt

# lead sample dataframe
df = sns.load_dataset('penguins', cache=False)

# display(df.head(3))
  species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g     sex
0  Adelie  Torgersen            39.1           18.7              181.0       3750.0    Male
1  Adelie  Torgersen            39.5           17.4              186.0       3800.0  Female
2  Adelie  Torgersen            40.3           18.0              195.0       3250.0  Female

# plot ecdf
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))

p1 = sns.ecdfplot(data=df, x='bill_length_mm', ax=ax1)
p1.set_title('Without hue')

p2 = sns.ecdfplot(data=df, x='bill_length_mm', hue='species', ax=ax2)
p2.set_title('Separated species by hue')

【讨论】:

【参考方案18】:

虽然这里有很多很好的答案,但我会包含一个更自定义的 ECDF 图

为经验累积分布函数生成值

import matplotlib.pyplot as plt

def ecdf_values(x):
    """
    Generate values for empirical cumulative distribution function
    
    Params
    --------
        x (array or list of numeric values): distribution for ECDF
    
    Returns
    --------
        x (array): x values
        y (array): percentile values
    """
    
    # Sort values and find length
    x = np.sort(x)
    n = len(x)
    # Create percentiles
    y = np.arange(1, n + 1, 1) / n
    return x, y
def ecdf_plot(x, name = 'Value', plot_normal = True, log_scale=False, save=False, save_name='Default'):
    """
    ECDF plot of x

    Params
    --------
        x (array or list of numerics): distribution for ECDF
        name (str): name of the distribution, used for labeling
        plot_normal (bool): plot the normal distribution (from mean and std of data)
        log_scale (bool): transform the scale to logarithmic
        save (bool) : save/export plot
        save_name (str) : filename to save the plot
    
    Returns
    --------
        none, displays plot
    
    """
    xs, ys = ecdf_values(x)
    fig = plt.figure(figsize = (10, 6))
    ax = plt.subplot(1, 1, 1)
    plt.step(xs, ys, linewidth = 2.5, c= 'b');
    
    plot_range = ax.get_xlim()[1] - ax.get_xlim()[0]
    fig_sizex = fig.get_size_inches()[0]
    data_inch = plot_range / fig_sizex
    right = 0.6 * data_inch + max(xs)
    gap = right - max(xs)
    left = min(xs) - gap
    
    if log_scale:
        ax.set_xscale('log')
        
    if plot_normal:
        gxs, gys = ecdf_values(np.random.normal(loc = xs.mean(), 
                                                scale = xs.std(), 
                                                size = 100000))
        plt.plot(gxs, gys, 'g');

    plt.vlines(x=min(xs), 
               ymin=0, 
               ymax=min(ys), 
               color = 'b', 
               linewidth = 2.5)
    
    # Add ticks
    plt.xticks(size = 16)
    plt.yticks(size = 16)
    # Add Labels
    plt.xlabel(f'name', size = 18)
    plt.ylabel('Percentile', size = 18)

    plt.vlines(x=min(xs), 
               ymin = min(ys), 
               ymax=0.065, 
               color = 'r', 
               linestyle = '-', 
               alpha = 0.8, 
               linewidth = 1.7)
    
    plt.vlines(x=max(xs), 
               ymin=0.935, 
               ymax=max(ys), 
               color = 'r', 
               linestyle = '-', 
               alpha = 0.8, 
               linewidth = 1.7)

    # Add Annotations
    plt.annotate(s = f'min(xs):.2f', 
                 xy = (min(xs), 
                       0.065),
                horizontalalignment = 'center',
                verticalalignment = 'bottom',
                size = 15)
    plt.annotate(s = f'max(xs):.2f', 
                 xy = (max(xs), 
                       0.935),
                horizontalalignment = 'center',
                verticalalignment = 'top',
                size = 15)
    
    ps = [0.25, 0.5, 0.75]

    for p in ps:

        ax.set_xlim(left = left, right = right)
        ax.set_ylim(bottom = 0)

        value = xs[np.where(ys > p)[0][0] - 1]
        pvalue = ys[np.where(ys > p)[0][0] - 1]

        plt.hlines(y=p, xmin=left, xmax = value,
                    linestyles = ':', colors = 'r', linewidth = 1.4);

        plt.vlines(x=value, ymin=0, ymax = pvalue, 
                   linestyles = ':', colors = 'r', linewidth = 1.4)
        
        plt.text(x = p / 3, y = p - 0.01, 
                 transform = ax.transAxes,
                 s = f'int(100*p)%', size = 15,
                 color = 'r', alpha = 0.7)

        plt.text(x = value, y = 0.01, size = 15,
                 horizontalalignment = 'left',
                 s = f'value:.2f', color = 'r', alpha = 0.8);

    # fit the labels into the figure
    plt.title(f'ECDF of name', size = 20)
    plt.tight_layout()
    

    if save:
        plt.savefig(save_name + '.png')

    
ecdf_plot(np.random.randn(100), name='Normal Distribution', save=True, save_name="ecdf")

其他资源:

ECDF Interpreting ECDF

【讨论】:

以上是关于如何绘制经验 cdf (ecdf)的主要内容,如果未能解决你的问题,请参考以下文章

通过从file:matplotlib读取值来绘制CDF

使用 Seaborn Python 绘制 CDF + 累积直方图

使用比数据点更少的标记进行绘图(或绘制 CDF 的更好方法?)[matplotlib,或一般绘图帮助]

R语言ggplot2可视化使用stat_ecdf函数可视化一个分布的ECDF经验累积概率分布函数图(Simple ECDF Plot with ggplot2)

r语言绘制核密度图怎么计算重叠

ECDF function