关于对Fisher Exact Test 的理解

本文介绍了费舍精确检验的基本概念,并通过实例详细解释了超几何分布的应用场景及计算方法,帮助读者理解该检验背后的概率原理。

http://en.wikipedia.org/wiki/Fisher's_exact_test

 

超几何分布:

 

 drawnnot drawntotal
whitekm-km
blackn-kN+k-m-nN-m
totalsnN-n

N


N个里有m个white,事件A={N里取n次,刚好取到k个white},事件A发生的概率:

 P(X=k) = {​{​{m /choose k} {​{N-m} /choose {n-k}}}/over {N /choose n}},

分母:N里取n个的状态个数,

分子:n里有k个是whiet,n-k个black的状态个数

P(X=k1)+P(X=k2)+...=1    k1,k2... all possible k values

 

 

Fisher Exact Test:

n个里有a+b个dieting的,事件A={n里取a+c个,其中刚好有a个dieting的},事件A发生的概率:

 

 menwomentotal
dietingaba + b
not dietingcdc + d
totalsa + cb + d

n


p = {​{​{a+b}/choose{a}}{​{c+d}/choose{c}}}/left/{​{​{n}/choose{a+c}}}/right.

 

自己理解:认为总数是n, a+c为样本大小,从总体拿出这么多样本,共有choose(n,a+c)种情况。

样本里有a个dieting的,有choose(a+b,a)种情况 ,有c个非dieting的有choose(c+d,c)种情况。

分母:n里取a+c个的状态个数,

分子:a+c里有a个是dieting,a+c-a个not dieting的状态个数

 

 

 2.

Exact Tests

The Hypergeometric Distribution

To understand Fisher Exact test, a review of the hypergeometric
distribution is first helpful.  Here is a typical example. A box of
chocolates contains 20 (N=20) pieces. Eight of them are known to be
caramels (M=8), and the remaining 12 pieces are nuts (N-M=12). If a person
selects 4 pieces (sample size n=4) at random, what is the distribution of
the number of caramels in the sample?

n=4  - sample size
M=8  - total number of caramels
N=20 - total number of chocolates

The distribution for the number of caramels in the sample of 4 can range
from 0 to 4 and the probability of 0, 1, 2, 3, or 4 caramels will occur
is:

No. in
Sample, Probability
   0    0.1022  = choose(8,0)*choose(12,4)/choose(20,4)
   1    0.3633  = choose(8,1)*choose(12,3)/choose(20,4)
   2    0.3814  = choose(8,2)*choose(12,2)/choose(20,4)
   3    0.1387  = choose(8,3)*choose(12,1)/choose(20,4)
   4    0.0144  = choose(8,4)*choose(12,0)/choose(20,4)
 Total  1.0000

The probabilities must sum to 1 as the total shows.

 

<think>我们遇到了一个错误:`fisher_exact() got an unexpected keyword argument 'replicates'`。这个错误通常发生在使用的SciPy版本低于1.4.0,因为在SciPy 1.4.0之前,`fisher_exact`函数并不支持`replicates`参数(实际上,在1.4.0版本中引入了`simulate_p_value`和`replicates`参数来处理大于2x2的列联表)。 在旧版本中,`fisher_exact`只能处理2x2的列联表,并且没有`simulate_p_value`和`replicates`参数。因此,当尝试使用这些参数时,会引发参数错误。 解决方案有两种: 1. 升级SciPy到1.4.0或更高版本(推荐)。 2. 如果无法升级,则对于大于2x2的列联表,使用其他方法(如卡方检验的蒙特卡洛模拟)或者自己实现费舍尔精确检验的蒙特卡洛方法。 这里,我们假设用户由于某些原因不能升级SciPy,因此我们需要修改代码以兼容旧版本。 修改思路: - 对于2x2的列联表,我们仍然可以使用`fisher_exact`(不带任何额外参数)。 - 对于大于2x2的列联表,我们可以使用`chi2_contingency`函数的蒙特卡洛模拟(如果其支持)或者使用其他方法(如自己实现蒙特卡洛模拟的费舍尔检验,但这比较复杂)。 但是,在旧版SciPy中,`chi2_contingency`函数有一个`simulate_p_value`参数(在较旧的版本中也可能没有,但比费舍尔精确检验的蒙特卡洛支持要早)?实际上,`chi2_contingency`的`simulate_p_value`参数在SciPy 0.15.0(2015年)就引入了。因此,我们可以使用卡方检验的蒙特卡洛模拟作为替代。 但注意:费舍尔精确检验和卡方检验是不同的检验。费舍尔精确检验基于超几何分布,而卡方检验是基于卡方分布。对于小样本或期望频数低的列联表,费舍尔精确检验更准确,但计算量大,尤其是大表时。而卡方检验的蒙特卡洛模拟是一种近似方法。 因此,我们可以这样调整: 1. 检查列联表是否为2x2:如果是,使用`fisher_exact`(不提供`simulate_p_value`和`replicates`)。 2. 如果不是2x2,则使用`chi2_contingency`的蒙特卡洛模拟(如果可用)或者使用其他方法。 然而,在旧版中,`chi2_contingency`的蒙特卡洛模拟可能也不可用?因此,我们需要检查SciPy版本,或者尝试使用参数。 另一种方法是:如果列联表大于2x2且旧版SciPy不支持`fisher_exact`的模拟,我们可以使用`statsmodels`库中的`fisher_exact`(如果安装了`statsmodels`)或者使用`rpy2`调用R的`fisher.test`函数。但这些会增加额外依赖。 考虑到依赖问题,我们选择以下策略: - 如果列联表是2x2,使用`scipy.stats.fisher_exact`。 - 否则,尝试使用`scipy.stats.chi2_contingency`的蒙特卡洛模拟(通过`simulate_p_value`参数,该参数在SciPy 0.15.0以上可用)。 - 如果`chi2_contingency`也不支持蒙特卡洛,则使用卡方检验的渐近方法(不推荐,因为小样本不准确)或者抛出一个错误。 但是,我们的目标是避免使用蒙特卡洛模拟的费舍尔精确检验吗?实际上,我们原本的代码逻辑是:当期望频数小于5的格子数超过一定数量时,使用费舍尔精确检验(蒙特卡洛模拟)。现在,由于旧版SciPy不支持,我们改用卡方检验的蒙特卡洛模拟。 因此,修改函数如下: 步骤: 1. 构建列联表。 2. 尝试计算期望频数(通过卡方检验的函数),并检查是否有期望频数小于5的格子(或者小于1的格子)。 3. 如果列联表是2x2,则使用费舍尔精确检验(不模拟)。 4. 如果不是2x2,且存在期望频数过低的情况,则尝试使用卡方检验的蒙特卡洛模拟(`chi2_contingency`的`simulate_p_value`和`replicates`参数)。 5. 如果卡方检验的蒙特卡洛模拟也不可用(例如,旧版SciPy没有这个参数),则使用渐近卡方检验(不准确)或者抛出一个错误。 但是,我们也可以检查一下`scipy.stats.chi2_contingency`是否支持`simulate_p_value`参数。我们可以通过检查函数签名或者使用try-except来实现。 具体代码修改: 我们将使用try-except来尝试调用带`simulate_p_value`的`chi2_contingency`,如果失败则回退到不带模拟的版本(并给出警告)。 注意:我们原本的代码逻辑是: if 期望频数条件不满足: 使用费舍尔精确检验(带蒙特卡洛模拟) else: 使用卡方检验 现在修改为: if 期望频数条件不满足: 如果是2x2表: 使用费舍尔精确检验(不带模拟) 否则: 尝试使用卡方检验的蒙特卡洛模拟(如果可用) 否则使用普通卡方检验(并给出警告) else: 使用卡方检验 代码示例: 注意:以下代码将整合到之前的函数中。 ```python import warnings from scipy.stats import chi2_contingency # 在函数内部: # ... 构建列联表 crosstab ... # 计算期望频数(使用chi2_contingency的渐近方法) _, p_chi, dof, expected = chi2_contingency(crosstab) # 检查期望频数 if (expected < 5).sum() > 0 or (expected < 1).sum() > 0: # 期望频数条件不满足 if crosstab.shape == (2,2): # 2x2表使用费舍尔精确检验 _, p = fisher_exact(crosstab) test_used = "Fisher's Exact (2x2)" else: # 尝试使用卡方检验的蒙特卡洛模拟 try: # 注意:chi2_contingency的蒙特卡洛模拟使用simulate_p_value参数 _, p, dof, expected = chi2_contingency(crosstab, simulate_p_value=True, replicates=reps) test_used = "Chi-square (Monte Carlo)" except TypeError: # 如果不支持参数,则使用渐近方法并警告 _, p, dof, expected = chi2_contingency(crosstab) test_used = "Chi-square (asymptotic) [Warning: small sample]" warnings.warn(f"Your SciPy version does not support Monte Carlo simulation for chi2 test. Using asymptotic method for {var}, which may be inaccurate.") else: # 使用普通卡方检验 _, p, dof, expected = chi2_contingency(crosstab) test_used = "Chi-square" ``` 但是,这里有一个问题:我们原本是希望用费舍尔精确检验,但现在用卡方检验的蒙特卡洛模拟代替。这两种检验方法不同,所以结果解释也会不同。因此,我们需要在结果中注明使用了什么方法。 另外,我们之前计算期望频数时已经调用了一次卡方检验(为了得到期望频数),然后根据条件可能再次调用卡方检验(蒙特卡洛模拟)。这会导致重复计算,效率较低。我们可以优化:第一次调用时,如果条件满足且不需要蒙特卡洛模拟,那么我们可以直接使用第一次的结果。但为了代码清晰,我们暂时不优化。 完整修改后的函数如下: 注意:我们不再使用`fisher_exact`的`simulate_p_value`和`replicates`参数,而是避免在非2x2表上使用费舍尔精确检验(除非升级SciPy)。 如果用户需要在大表上使用费舍尔精确检验,强烈建议升级SciPy。 下面是修改后的函数: ```python import pandas as pd from scipy import stats import numpy as np from statsmodels.stats.multitest import multipletests import warnings def cluster_association_test(df, cluster_col, cat_vars, alpha=0.05, reps=10000): """ 兼容旧版本SciPy的聚类关联性检验 参数: df -- 包含聚类结果和分类变量的DataFrame cluster_col -- 聚类分组的列名 cat_vars -- 要检验的分类变量列表 alpha -- 显著性水平 (默认0.05) reps -- 蒙特卡洛模拟次数 (默认10000) 返回: 包含检验结果的DataFrame """ results = [] for var in cat_vars: # 构建列联表 crosstab = pd.crosstab(df[cluster_col], df[var]) # 初始化变量 chi2 = np.nan p = np.nan dof = np.nan expected = np.nan test_used = "Unknown" try: # 首先使用卡方检验计算期望频数(渐近方法) # 注意:这里我们只为了期望频数,所以使用渐近方法 _, p_chi, dof_chi, expected_vals = stats.chi2_contingency(crosstab) # 检查期望频数条件 if (expected_vals < 5).sum() > 0 or (expected_vals < 1).sum() > 0: # 期望频数条件不满足 if crosstab.shape == (2, 2): # 2x2表使用费舍尔精确检验 _, p = stats.fisher_exact(crosstab) test_used = "Fisher's Exact (2x2)" # 对于费舍尔精确检验,我们无法直接得到期望频数,所以使用之前计算的 expected = expected_vals dof = dof_chi # 2x2表的自由度是1 chi2 = None # 费舍尔检验没有卡方统计量 else: # 尝试使用卡方检验的蒙特卡洛模拟 try: # 使用蒙特卡洛卡方检验 chi2, p, dof, expected = stats.chi2_contingency( crosstab, simulate_p_value=True, replicates=reps ) test_used = "Chi-square (Monte Carlo)" except TypeError: # 如果不支持蒙特卡洛,则使用渐近卡方检验 chi2, p, dof, expected = stats.chi2_contingency(crosstab) test_used = "Chi-square (asymptotic)" warnings.warn(f"For variable {var}, the table is larger than 2x2 and expected frequencies are low. " f"Monte Carlo simulation is not available in your SciPy version. Using asymptotic chi-square, which may be inaccurate.") else: # 期望频数满足,使用渐近卡方检验 chi2, p, dof, expected = stats.chi2_contingency(crosstab) test_used = "Chi-square" except Exception as e: # 处理所有异常情况 test_used = f"Error: {str(e)}" p = np.nan # 记录结果 results.append({ "variable": var, "test_used": test_used, "chi2": chi2, # 如果是费舍尔精确检验,这里为None "p_value": p, "dof": dof, "crosstab_shape": crosstab.shape }) # 转换为DataFrame results_df = pd.DataFrame(results) # 多重比较校正 if not results_df.empty: reject, pvals_corrected, _, _ = multipletests( results_df["p_value"], alpha=alpha, method="bonferroni" ) results_df["p_value_corrected"] = pvals_corrected results_df["significant"] = reject else: results_df["p_value_corrected"] = np.nan results_df["significant"] = False results_df["significance_level"] = alpha return results_df ``` ### 代码说明 1. **期望频数计算**: - 首先使用`stats.chi2_contingency`(不带模拟)计算期望频数,用于判断条件。 2. **检验选择逻辑**: - **2x2表且期望频数低**:使用费舍尔精确检验(标准方法) - **大于2x2表且期望频数低**: - 尝试使用卡方检验的蒙特卡洛模拟(`simulate_p_value=True`) - 失败则回退到渐近卡方检验(并警告) - **期望频数满足**:使用渐近卡方检验 3. **结果记录**: - 记录使用的检验方法(`test_used`),便于用户了解实际使用的检验 - 对于费舍尔精确检验,卡方统计量(`chi2`)设为None(因为该检验不产生卡方统计量) 4. **异常处理**: - 捕获所有异常,避免单个变量导致整个分析失败 ### 注意事项 - 该方案在旧版SciPy中解决了`replicates`参数错误问题,但牺牲了非2x2表的费舍尔精确检验(改用卡方检验的蒙特卡洛模拟)。 - 如果用户需要严格的费舍尔精确检验,建议升级SciPy到1.4.0以上。 ### 替代方案:使用statsmodels(如果可用) 如果环境中安装了`statsmodels`,我们可以使用其`fisher_exact`函数(支持任意大小的列联表)。但注意,`statsmodels`的`fisher_exact`在旧版本中可能也不支持蒙特卡洛模拟,需要版本≥0.14.0(2023年发布)。 ```python # 可选:使用statsmodels的fisher_exact(如果可用) try: from statsmodels.stats.contingency_tables import Table table = Table(crosstab.values) # 注意:statsmodels中,费舍尔精确检验的蒙特卡洛方法参数为'monte_carlo' p = table.fisher_exact(method='monte_carlo', replicates=reps)[1] test_used = "Fisher's Exact (monte carlo, statsmodels)" except ImportError: # 回退到上述方法 pass ``` 考虑到依赖问题,我们在代码中没有强制使用,但可以作为备选。 §§相关问题§§: 1. 在统计检验中,费舍尔精确检验和卡方检验的主要区别是什么?各自适用的条件是什么? 2. 如何检查Python函数的参数签名(以判断某个参数是否存在)? 3. 在Python中,如何处理不同版本库的兼容性问题?有哪些最佳实践? 4. 除了SciPy和statsmodels,还有哪些Python库提供高级统计检验功能?</think>### 解决 `fisher_exact() got an unexpected keyword argument 'replicates'` 错误 这个错误表明您使用的 SciPy 版本低于 1.4.0,因为 `replicates` 参数是在 SciPy 1.4.0 中与 `simulate_p_value` 一起引入的。以下是兼容旧版本 SciPy 的解决方案: #### 完整解决方案代码 ```python import pandas as pd import numpy as np from scipy import stats from statsmodels.stats.multitest import multipletests import warnings def cluster_association_test(df, cluster_col, cat_vars, alpha=0.05, reps=10000): """ 兼容旧版本SciPy的聚类关联性检验 参数: df -- 包含聚类结果和分类变量的DataFrame cluster_col -- 聚类分组的列名 cat_vars -- 要检验的分类变量列表 alpha -- 显著性水平 (默认0.05) reps -- 蒙特卡洛模拟次数 (默认10000) 返回: 包含检验结果的DataFrame """ results = [] for var in cat_vars: # 构建列联表 crosstab = pd.crosstab(df[cluster_col], df[var]) n_rows, n_cols = crosstab.shape # 初始化变量 chi2 = np.nan p = np.nan dof = np.nan expected = np.nan test_used = "Unknown" try: # 检查表大小和期望频数 _, _, dof, expected = stats.chi2_contingency(crosstab) low_expected = (expected < 5).sum() > 0 # 2x2表处理 if n_rows == 2 and n_cols == 2: _, p = stats.fisher_exact(crosstab) test_used = "Fisher's Exact (2x2)" # 大于2x2表且期望频数低 elif low_expected: # 尝试使用新版本SciPy的费舍尔精确检验 try: _, p = stats.fisher_exact(crosstab, simulate_p_value=True, replicates=reps) test_used = "Fisher's Exact (simulated)" except TypeError: # 尝试使用statsmodels作为替代 try: from statsmodels.stats.contingency_tables import Table table = Table(crosstab.values) p = table.fisher_exact(method='monte_carlo', replicates=reps)[1] test_used = "Fisher's Exact (statsmodels)" except ImportError: # 回退到卡方检验的蒙特卡洛模拟 try: _, p, _, _ = stats.chi2_contingency( crosstab, simulate_p_value=True, replicates=reps ) test_used = "Chi-square Monte Carlo" except TypeError: # 最终回退到普通卡方检验 _, p, _, _ = stats.chi2_contingency(crosstab) test_used = "Chi-square (fallback)" warnings.warn(f"Using fallback chi-square for {var} due to old SciPy version") # 期望频数满足条件 else: _, p, _, _ = stats.chi2_contingency(crosstab) test_used = "Chi-square" except Exception as e: test_used = f"Error: {str(e)}" p = np.nan # 记录结果 results.append({ "variable": var, "test_used": test_used, "p_value": p, "crosstab_shape": f"{n_rows}x{n_cols}", "replicates": reps if "simulated" in test_used or "Monte Carlo" in test_used else np.nan }) # 转换为DataFrame results_df = pd.DataFrame(results) # 多重比较校正 if not results_df.empty: reject, pvals_corrected, _, _ = multipletests( results_df["p_value"], alpha=alpha, method="bonferroni" ) results_df["p_value_corrected"] = pvals_corrected results_df["significant"] = reject else: results_df["p_value_corrected"] = np.nan results_df["significant"] = False results_df["significance_level"] = alpha return results_df # 使用示例 # 假设 df 是包含聚类结果和分类变量的DataFrame # cluster_col = "cluster" # cat_vars = ["category1", "category2", "category3"] # results = cluster_association_test(df, cluster_col, cat_vars) # print(results) ``` ### 代码解释 #### 1. 多层回退策略 ```python # 尝试使用新版本SciPy的费舍尔精确检验 try: _, p = stats.fisher_exact(crosstab, simulate_p_value=True, replicates=reps) test_used = "Fisher's Exact (simulated)" # 尝试使用statsmodels作为替代 except TypeError: try: from statsmodels.stats.contingency_tables import Table table = Table(crosstab.values) p = table.fisher_exact(method='monte_carlo', replicates=reps)[1] test_used = "Fisher's Exact (statsmodels)" # 回退到卡方检验的蒙特卡洛模拟 except ImportError: try: _, p, _, _ = stats.chi2_contingency( crosstab, simulate_p_value=True, replicates=reps ) test_used = "Chi-square Monte Carlo" # 最终回退到普通卡方检验 except TypeError: _, p, _, _ = stats.chi2_contingency(crosstab) test_used = "Chi-square (fallback)" ``` 这个多层异常处理实现了以下回退策略: 1. 尝试使用新版本 SciPy 的费舍尔精确检验 2. 失败则尝试使用 statsmodels 库 3. 再失败则尝试卡方检验的蒙特卡洛模拟 4. 最终回退到普通卡方检验 #### 2. 表大小和期望频数检查 ```python # 检查表大小和期望频数 _, _, dof, expected = stats.chi2_contingency(crosstab) low_expected = (expected < 5).sum() > 0 # 2x2表处理 if n_rows == 2 and n_cols == 2: _, p = stats.fisher_exact(crosstab) test_used = "Fisher's Exact (2x2)" ``` 这段代码: 1. 计算期望频数以确定是否需要精确检验 2. 对 2x2 表直接使用标准费舍尔精确检验 3. 对大于 2x2 且期望频数低的表使用精确检验的蒙特卡洛模拟 #### 3. 结果记录 ```python results.append({ "variable": var, "test_used": test_used, # 记录实际使用的检验方法 "p_value": p, "crosstab_shape": f"{n_rows}x{n_cols}", # 记录表大小 "replicates": reps if "simulated" in test_used or "Monte Carlo" in test_used else np.nan }) ``` 特别记录了: - 实际使用的检验方法 (`test_used`) - 列联表大小 (`crosstab_shape`) - 蒙特卡洛模拟次数 (`replicates`) ### 替代解决方案:使用 statsmodels 如果无法升级 SciPy,安装 statsmodels 是更好的选择: ```bash pip install statsmodels ``` 然后使用以下代码实现费舍尔精确检验: ```python from statsmodels.stats.contingency_tables import Table def fisher_exact_with_simulation(crosstab, reps=10000): """使用statsmodels实现费舍尔精确检验的蒙特卡洛模拟""" table = Table(crosstab.values) return table.fisher_exact(method='monte_carlo', replicates=reps)[1] # 示例使用 crosstab = pd.crosstab(df['cluster'], df['category']) p_value = fisher_exact_with_simulation(crosstab, reps=10000) ``` ### 版本检查与升级建议 检查 SciPy 版本: ```python import scipy print(scipy.__version__) # 需要 >=1.4.0 # 检查功能是否可用 has_fisher_simulate = hasattr(stats.fisher_exact, '__code__') and 'simulate_p_value' in stats.fisher_exact.__code__.co_varnames print(f"Fisher exact with simulation supported: {has_fisher_simulate}") ``` 升级 SciPy: ```bash # 升级到最新版本 pip install --upgrade scipy # 或升级到特定版本 pip install scipy==1.10.0 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值