【非参】python实现直方图估计

开新坑,写一下非参课程的实验。以下是我的简易理解,不会写的太深入,可能会有错误疏漏,欢迎各位指出。若想深入了解非参的话,还是建议大家找找教材和论文进行阅读。

在开始讲直方图估计之前,先讲一些非参的基本概念。

1、非参数模型和参数模型的区别是什么?

参数模型假设总体的分布类型已知,分布的参数未知,抽取样本数据来估计分布的参数,并用假设检验来检验未知参数的合理性。

然而实际生活中的很多数据它的分布类型我们是不知道的,因而参数模型的适用范围较窄,但统计检验的效力较强。

非参数模型则不需要对数据的总体分布进行假设,理论上适用于任何数据,因此适用范围非常广。

优点

  • 适用范围广,较为稳健。

缺点

  • 维数诅咒或维数灾难,对于高维度的数据(即变量数目较多),非参数模型计算非常缓慢。
  • 在模型假设(即分布假设)正确的前提下,参数模型的表现更佳。
    请添加图片描述

2、非参有什么用?

这个问题问得比较功利,但却可以帮助大家快速了解非参的作用(没用还学个锤子)。根据我的浅薄理解,目前非参主要有以下用途:

  • (1)估计分布函数 F ( x ) = P ( X ≤ x ) F(x)=P(X\leq x) F(x)=P(Xx)
  • (2)估计概率密度函数 f ( x ) f(x) f(x)
  • (2)回归拟合及预测。
  • (3)做检验。

由于非参的检验效力相对较弱,常见的用途主要为前三条。拟合及预测的作用可以参考线性回归,根据给定的数据X来预测Y。非参还引入了连接函数(link function),增加了非线性,提高了模型的估计能力,但也带来了可解释性降低的问题。

(1)和(2)的作用相同,可以获取数据的分布情况信息。如何得知数据的分布情况呢?最简单的方法就是画个图,看看数据的分布情况,看数据在哪个区间的分布比较密集,哪个区间的分布比较稀疏。直方图估计主要的用途就是估计数据的分布。

3、直方图估计

3.1 思想

直方图估计的思想非常简单,选定一个初始点 x 0 x_0 x0,将数据划分成等长的若干区间 B j = [ x 0 + ( j − 1 ) h , x 0 + h ) , j ∈ Z , B_j = [x_0+(j-1)h, x_0 + h), j\in Z, Bj=[x0+(j1)h,x0+h),jZ,然后计算落在每个区间上的样本点个数(即频数) n j n_j nj,除以样本容量 n n n得到频率,再除以区间长度 h h h即可得到每个区间的估计概率密度函数 f j = n j n h . f_j = \frac{n_j}{nh}. fj=nhnj.
用数学公式表达为:
f ^ h ( x ) = 1 n h ∑ i = 1 n ∑ j I ( X i ∈ B j ) I ( x ∈ B j ) , \hat{f}_h(x) = \frac{1}{nh}\sum_{i=1}^{n} \sum_j I(X_i \in B_j) I(x\in B_j), f^h(x)=nh1i=1njI(XiBj)I(xBj),
其中,
I ( X i ∈ B J ) = { 1 i f X i ∈ B j , 0 o t h e r w i s e . I(X_i \in B_J) = \begin{cases} 1 & if \quad X_i \in B_j, \\ 0 & otherwise. \end{cases} I(XiBJ)={10ifXiBj,otherwise.
这里 I ( X i ∈ B J ) I(X_i \in B_J) I(XiBJ)是示性函数,表示如果 X i X_i Xi B j B_j Bj区间内则为1,否则为0。这个 f ^ h ( x ) \hat{f}_h(x) f^h(x) 的计算公式看起来比较复杂,其表达的意思是:先判断 x x x在哪个区间,然后加总求和这个区间内的所有样本,从而得到这个区间的样本频数,最后套用上文公式计算得到概率密度函数 f ^ h ( x ) \hat{f}_h(x) f^h(x)

另一种更为简单的表达方式为:
f ^ h ( m j ) = 1 n h # { X i ∈ [ m j − h 2 , m j + h 2 ) } . \hat{f}_h(m_j) = \frac{1}{nh} \# \{X_i \in [m_j - \frac{h}{2}, m_j + \frac{h}{2}) \}. f^h(mj)=nh1#{Xi[mj2h,mj+2h)}.

# { } \#\{\} #{}号表示是满足 { } \{\} {}内表达式的样本个数。 m j m_j mj为第 j j j个区间的中心点。该方法先求解出落在以 m j m_j mj为中心点的区间的样本个数,然后除以 n h nh nh得到概率密度函数 f ^ h ( m j ) \hat{f}_h(m_j) f^h(mj)

3.2 优缺点

优点

  • 简单直接,容易理解
  • 直方图能够非常方便地观察数据的分布情况

缺点

  • 直方图的形状依赖于窗宽 h h h和原点 x 0 x_0 x0
  • 直方图估计的密度函数不是连续的,在跳跃处是不可导的
  • 将连续数据离散化,每一个 x ∈ [ m j − h 2 , m j + h 2 ) x\in [m_j - \frac{h}{2}, m_j + \frac{h}{2}) x[mj2h,mj+2h)的估计 f f f都是 f ^ h ( m j ) \hat{f}_h (m_j) f^h(mj)

3.3 代码实现

方式一:懒人版,直接调包

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

np.random.seed(0)

n = 200
x = np.random.normal(0, 0.1, n)

plt.figure(figsize=(10, 7), dpi=80)
plt.rc('font', family='Times New Roman', weight = 'medium', size=14)  #设置英文字体
plt.hist(x,edgecolor='white')
plt.xlabel('x')
plt.ylabel('y')
plt.title(f'matplotlib hist')

plt.show()

在这里插入图片描述

seaborn distplot
plt.figure(figsize=(10, 7), dpi=80)
sns.distplot(a=x, kde=True)  #kde = True 增加密度线
plt.xlabel('x')
plt.ylabel('y')
plt.title(f'seaborn distplot')
plt.show()

在这里插入图片描述

方式二:我的代码实现

定义直方图类
class Histogram():
    def __init__(self, verbose=False):
        self.verbose = verbose #是否输出计算过程,默认不输出,default: False

    def check(self, x, lower, upper):
        '''
        示性函数,判断x是否落在区间[lower, upper) 里,是返回1,否返回0。
        '''
        if x >= lower and x < upper:
            return 1
        else:
            return 0

    def find_left(self, x, m_j, h):
        # 向左搜索
        center_left = []  #记录左侧的中点,记得要排序
        fh_list = []  #记录每一个箱子的密度估计值f_h

        lower = m_j - h / 2
        upper = m_j + h / 2

        while lower > x.min() - h:
            f = np.array([self.check(i, lower, upper) for i in x])  #示性函数,判断x的每一个值是否落在当前区间
            fh = f.sum() / (n * h)
            if self.verbose:
                print(f'Left: m_j = {m_j}, h = {h}, lower = {lower}, upper = {upper}, f.sum = {f.sum()}, fh = {fh}')
            center_left.append(m_j)
            fh_list.append(fh)
            
            m_j -= h
            lower = m_j - h / 2
            upper = m_j + h / 2

        center_left = [center_left[-i] for i in range(1, len(center_left)+1)]  #翻转
        fh_list = [fh_list[-i] for i in range(1, len(fh_list)+1)]

        return np.array(center_left), np.array(fh_list)

    def find_right(self, x, m_j, h):
        # 向右搜索
        center_right = []  #记录右侧的中点,不需要排序
        fh_list = []  #记录每一个箱子的密度估计值f_h

        lower = m_j - h / 2
        upper = m_j + h / 2

        while upper < x.max() + h:
            f = np.array([self.check(i, lower, upper) for i in x])  #示性函数,判断x的每一个值是否落在当前区间
            fh = f.sum() / (n * h)
            if self.verbose:
                print(f'Right: m_j = {m_j}, h = {h}, lower = {lower}, upper = {upper}, f.sum = {f.sum()}, fh = {fh}')
            center_right.append(m_j)
            fh_list.append(fh)
            
            m_j += h
            lower = m_j - h / 2
            upper = m_j + h / 2

        return np.array(center_right), np.array(fh_list)

    def fit(self, x, m_j, h):
        '''
        直方图拟合函数。
        Fit the data with hitogram nonparametric method.

        input:
        x: the data, must be a vector, ndarray, [n, ]. 数据,必须为一个向量,numpy数组。
        m_j: the inital center points, float or int. 初始数据中心。
        h: the window length, float or int. 窗宽参数。

        output:
        centers: each center of bins, ndarray, [n, ]. 每一个箱子的中心点m_j。
        fh_list: the estimated density corrensponding to each center, ndarray, [n,]. 每一个中心点对应的频率密度估计值。
        '''

        assert x.ndim == 1

        self.h = h
        self.x0 = m_j
        if m_j < x.min():
            centers, fh_list = self.find_right(x, m_j, h)
        elif m_j > x.max():
            centers, fh_list = self.find_left(x, m_j, h)
        else:
            c_left, fh_left = self.find_left(x, m_j, h)
            c_right, fh_right = self.find_right(x, m_j, h)
            centers = np.concatenate((c_left, c_right[1:]))
            fh_list = np.concatenate((fh_left, fh_right[1:]))
        
        self.centers = centers
        self.fh_list = fh_list

        return centers, fh_list

    def plot(self, saveflag=False):
        # 画图函数
        plt.figure(figsize=(10, 7), dpi=80)
        plt.rc('font', family='Times New Roman', weight = 'medium', size=14)  #设置英文字体
        # plt.plot(self.centers, self.fh_list, c='gold', marker='o')  
        plt.bar(self.centers, self.fh_list, width=self.h, edgecolor='white')  
        plt.xlabel('x')
        plt.ylabel('y')
        plt.title(f'h = {self.h}, x0 = {self.x0}')

        if saveflag:
            plt.savefig('histogram.png')
        else:
            plt.show()
测试
x0 = 0
h = 0.05
estimator = Histogram(verbose=True)
centers, fh_list = estimator.fit(x, x0, h)
estimator.plot()

在这里插入图片描述

尝试不同的区间宽度h
x0 = 0
h_list = [0.007, 0.05, 0.02, 0.1]
estimator = Histogram(verbose=False)
centers_set = []
fh_set = []
for h in h_list:
    centers, fh_list = estimator.fit(x, x0, h)
    centers_set.append(centers)
    fh_set.append(fh_list)

plt.figure(figsize=(16, 12), dpi=80)
plt.rc('font', family='Times New Roman', weight = 'medium', size=14)  #设置英文字体

for i in range(len(h_list)):
    ax = plt.subplot(2, 2, i+1)
    ax.bar(centers_set[i], fh_set[i], width=h_list[i], edgecolor='white')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_title(f'h = {h_list[i]}, x0 = {x0}')

plt.show()

在这里插入图片描述

总结

这次尝试按着书上的公式去实现了直方图估计,代码写的可能有点冗余,不够精简,但基本上达到了我的期望水平,能够按自己意愿任意修改起始点 x 0 x_0 x0和窗宽 h h h,完成实验作业。代码还有优化空间,但继续优化有点重复造轮子的感觉,真要实际工作中用还是调包快,我就不继续优化了~

参考资料:

[1] 【知乎】参数检验与非参数检验的区别与联系,各自的优缺点,如何选择?
[2] Härdle W, Müller M, Sperlich S, et al. Nonparametric and semiparametric models[M]. Berlin: Springer, 2004.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin Davis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值