一、算法思想
1、特征选择
特征选择是去除无关紧要或庸余的特征,仍然还保留其他原始特征,从而获得特征子集,从而以最小的性能损失更好地描述给出的问题。
特征选择方法可以分为三个系列:过滤式选择、包裹式选择和嵌入式选择的方法 。
本文介绍的卡方检验即为过滤式的特征选择算法。
关于过滤式的特征算法系列,可参考我的其他文章。
特征选择之互信息特征选择之Fisher Score
2、卡方检验
卡方检验介绍
卡方是由英语"Chi_Square"音译而来,Chi是希腊字母X(读作kai),所以卡方又能写成 。
卡方检验是一种常用的假设检验方法,它的无效假设是:观察频数与期望频数没有差别。所谓无效假设又称零假设(null hypothesis),一般是有意推翻的、希望证明其错误的假设。
如,我们希望证明两个明显相关的变量具有相关性,我们可以做出无效假设:假设二者无关。
举个例子,我们希望证明新型冠状病毒与最近的时事相关。我们可以做出无效假设,二者无关,即最近的时事没有新型冠状病毒的信息。然后我们通过我们的收集数据分析计算证明得出假设失效。这样就证明了这两者具有相关性。
检验过程
卡方检验通常用来检验两个变量之间有没有关系。
那么如何检验呢?
首先我们做出预测,得到期望频数。观察实际情况,得到观察频数。
检验过程如下:观察频数与期望频数通过如下公式可得到值,它表示观察值与理论值之间的偏差程度:
observed为观察频数,expected为期望频数。
另外我们还引入另一个概念,自由度。自由度指的是不受限制的变量的个数。一般情况下,自由度=(行数-1)*(列数-1)。如下图的一个例子:
最后由的值和自由度可以查表得到p值(大学课本上就是这样)。当然也可以通过积分卡方函数得到精确结果,但是这比较复杂。
如果p值很小,观察值与理论值偏离程度太大。说简单点,p越大,观察值与理论值偏离程度越小。两个变量间越没有差别,它俩具有很强的“相关性”
如果还不太懂,可参考以下链接内的视频。↓
参考视频
特征选择中的卡方检验
由上文所述, 特征选择是去除无关紧要或庸余的特征,仍然还保留其他原始特征,从而获得特征子集,从而以最小的性能损失更好地描述给出的问题。
这里我们将样本中的特征对应观察值,标签类对应理论值。如果某个特征对应属于某个类别,那么他与标签类“越没有差别”。或者反过来说,如果某个特征与某个标签类“越没有差别”,那么我们说这个特征对应属于这个标签类。
例如瓜的类别有好坏之分,其某个特征表面光滑,与“好“”这个类“越没有差别”,我们就说这个表面光滑这个特征属于好瓜这个标签。
这里说的“越没有差别”,即是上文说的p值越大,观察值与理论值偏离程度越小。两个变量间越没有差别,它俩具有很强的“相关性”
因此可以根据不同特征与标签类通过卡方检验得到的p值的大小来排名,排名越高,说明该特征更有可能属于标签类。
二、代码实现
1、python调用scipy包
python里面的scipy包里有卡方检验的函数:stats.chisquare()
输入参数为两个列表obs,exp。列表元素为其相应的频数。
from scipy import stats
obs=[8,7,7]
exp=[8,8,8]
print(stats.chisquare(obs,exp,))
结果:
Power_divergenceResult(statistic=0.25,pvalue=0.8824969025845955)
其中的statistics就是卡方值,可这样求出stats.chisquare(obs,exp,)[0]
pvalue即是p值,stats.chisquare(obs,exp,)[1]
2、自己实现python代码
话不多说,上代码。
import math
import numpy as np
# 自己实现chisquare,参数为两个列表obs,exp,返回为包含卡方值和p值的列表
#eg:obs=[8,7,7] ,exp=[8,8,8]
#return [0.25, 0.8824969025845955]
def my_chisquare(obs, exp):
# 将列表转化为numpy.ndarray类型
obs = np.atleast_1d(np.asanyarray(obs))
exp = np.atleast_1d(np.asanyarray(exp))
if obs.size != exp.size:
print('The size of the obs and the exp array is not equal')
exit()
# 得到ndarray类型,得到各项的理论与观察的相对偏离距离,相加即为卡方值
terms = (obs - exp) ** 2 / exp
# 求得卡方值,numpy.float64类型
stat = terms.sum(axis=0)
# 计算obs,exp的维度
num_obs = terms.size
# 调用自己写的求p值的函数,得到p值
p = chisqr2pValue(num_obs - 1, stat)
chisquare_list = []
chisquare_list.append(stat)
chisquare_list.append(p)
return chisquare_list
# 用斯特林公式来近似求伽马函数(卡方检验)
def getApproxGamma(n):
RECIP_E = 0.36787944117144232159552377016147
TWOPI = 6.283185307179586476925286766559
d = 1.0 / (10.0 * n)
d = 1.0 / ((12 * n) - d)
d = (d + n) * RECIP_E
d = math.pow(d, n)
d = d * math.sqrt(TWOPI / n)
return d
# 不完全伽马函数中需要调用的函数(卡方检验)
def KM(s, z):
_sum = 1.0
log_nom = math.log(1.0)
log_denom = math.log(1.0)
log_s = math.log(s)
log_z = math.log(z)
for i in range(1000):
log_nom += log_z
s = s + 1
log_s = math.log(s)
log_denom += log_s
log_sum = log_nom - log_denom
log_sum = float(log_sum)
_sum += math.exp(log_sum)
return _sum
# 不完全伽马函数,采用计算其log值(卡方检验)
def log_igf(s, z):
if z < 0.0:
return 0.0
sc = float((math.log(z) * s) - z - math.log(s))
k = float(KM(s, z))
return math.log(k) + sc
#卡方检验求p值
# dof是自由度,chi_squared为卡方值,该函数实现知道自由度和卡方值求p值
# 核心是用不完全伽马函数除以伽马函数,两者都采用近似函数求解
# 参见
def chisqr2pValue(dof, chi_squared):
dof = int(dof)
chi_squared = float(chi_squared)
if dof < 1 or chi_squared < 0:
return 0.0
k = float(dof) * 0.5
v = chi_squared * 0.5
# 自由度为2时
if dof == 2:
return math.exp(-1.0 * v)
# 不完全伽马函数,采用计算其log值
incompleteGamma = log_igf(k, v)
# 如果过小或者无穷
if math.exp(incompleteGamma) <= 1e-8 or math.exp(incompleteGamma) == float('inf'):
return 1e-14
# 完全伽马函数,用斯特林公式近似
gamma = float(math.log(getApproxGamma(k)))
incompleteGamma = incompleteGamma - gamma
if math.exp(incompleteGamma) > 1:
return 1e-14
pvalue = float(1.0 - math.exp(incompleteGamma))
return pvalue
obs1 = [8, 7, 7]
exp1 = [8, 8,8]
lst = my_chisquare(obs1, exp1)
print(lst)
主要是利用了近似函数来求p的值。主要公式如下:
需要详细的数学公式可参考这个博客参考博客
最后注意,卡方检验中的期望值理论上不能为0,否则会使得卡方值为无穷大,无实际意义。读者输入的期望标签可以根据实际情况调整,避免出现0的情况。
希望本文对你有帮助!