首先,对于卡方检验,您的数据是正数、负数、字符串还是任何其他类型都无关紧要,只要它是离散的(或很好地分箱)即可。这是因为卡方检验计算基于列联表而不是您的原始数据。sklearn.feature_selection.chi2的文档和相关的使用示例根本不清楚。不仅如此,两者在输入数据的类型上也不一致(文档中说的是布尔值或频率,而该示例使用的是原始 iris 数据集,其数量以厘米为单位),因此这会导致更多的混乱。sklearn 的卡方仅期望非负特征的原因很可能是实现:作者依赖于逐行求和,这意味着允许负值会产生错误的结果。一些难以理解的优化也在内部发生,所以为了简单的特性选择,我个人会选择scipy 的 implementation。
由于您的数据不是离散的,因此您必须将每个特征分类到一定数量的名义类别中才能执行卡方检验。请注意,无论您采用何种技术,在此步骤中都会发生信息丢失;您的目标是通过找到最适合您的数据的方法来最小化它。您还必须了解,结果不能被视为绝对真理,因为测试不是为连续性质的数据设计的。另一个肯定会影响你的特征选择过程的大问题是特征的数量大于观察的数量。我肯定会推荐看看sklearn 的分解方法比如 PCA 来减少特征的数量,如果你的特征是分组的,你可以尝试多因素分析(Python 实现可通过Prince获得)。
现在已经不碍事了,让我们看一个使用 iris 数据集进行简单特征选择的示例。我们将在构造的数据框中添加一个无用的正态分布变量进行比较。
import numpy as np
import scipy as sp
import pandas as pd
from sklearn import datasets, preprocessing as prep
iris = datasets.load_iris()
X, y = iris['data'], iris['target']
df = pd.DataFrame(X, columns= iris['feature_names'])
df['useless_feature'] = np.random.normal(0, 5, len(df))
现在我们必须对数据进行分类。对于基于值和基于分位数的分箱,您可以分别使用pd.cut和pd.qcut(这个很好的答案解释了两者之间的区别),但 sklearn 的KBinsDiscretizer提供了更多选项。在这里,我将它用于一维k 均值聚类以创建 bin(对每个特征进行单独计算):
def bin_by_kmeans(pd_series, n_bins):
binner = prep.KBinsDiscretizer(n_bins= n_bins, encode= 'ordinal', strategy= 'kmeans')
binner.fit(pd_series.values.reshape(-1, 1))
bin_edges = [
'({:.2f} .. {:.2f})'.format(left_edge, right_edge)
for left_edge, right_edge in zip(
binner.bin_edges_[0][:-1],
binner.bin_edges_[0][1:]
)
]
return list(map(lambda b: bin_edges[int(b)], binner.transform(pd_series.values.reshape(-1, 1))))
df_binned = df.copy()
for f in df.columns:
df_binned[f] = bin_by_kmeans(df_binned[f], 5)
调查各个特征的分箱情况的一个好方法是计算每个分箱 ( ) 中的数据点数,df_binned['feature_name_here'].value_counts()并打印出给定特征和标签列的pd.crosstab(列联表)。
该计算的有效性经常被引用的准则是,只有在每个单元中观察到的和预期的频率至少为 5 时才应使用该测试。
因此,您在列联表中看到的零越多,卡方结果的准确性就越低。这将需要一些手动调整。
接下来是对两个变量的独立性执行卡方检验的函数(本教程有非常有用的解释,强烈推荐阅读,代码从那里提取):
def get_chi_squared_results(series_A, series_B):
contingency_table = pd.crosstab(series_A, series_B)
chi2_stat, p_value, dof, expected_table = sp.stats.chi2_contingency(contingency_table)
threshold = sp.stats.chi2.ppf(0.95, dof)
return chi2_stat, threshold, p_value
要关注的值是统计数据本身、阈值及其 p 值。阈值是从分位数函数中获得的。您可以使用这三个来对单个特征标签测试进行最终评估:
print('{:<20} {:>12} {:>12}\t{:<10} {:<3}'.format('Feature', 'Chi2', 'Threshold', 'P-value', 'Is dependent?'))
for f in df.columns:
chi2_stat, threshold, p_value = get_chi_squared_results(df[f], y)
is_over_threshold = chi2_stat >= threshold
is_result_significant = p_value <= 0.05
print('{:<20} {:>12.2f} {:>12.2f}\t{:<10.2f} {}'.format(
f, chi2_stat, threshold, p_value, (is_over_threshold and is_result_significant)
))
就我而言,输出如下所示:
Feature Chi2 Threshold P-value Is dependent?
sepal length (cm) 156.27 88.25 0.00 True
sepal width (cm) 89.55 60.48 0.00 True
petal length (cm) 271.80 106.39 0.00 True
petal width (cm) 271.75 58.12 0.00 True
useless_feature 300.00 339.26 0.46 False
为了声明两个变量之间的依赖关系,结果统计量应大于阈值,p 值应低于 0.05。您可以选择较小的 p 值以获得更高的置信度(您必须sp.stats.chi2.ppf据此计算阈值),但 0.05 是您的结果被视为显着所需的“最大”值。就有用特征的排序而言,请考虑查看计算的统计量与每个特征的阈值之间差异的相对幅度。