简介:中医证型关联规则挖掘的Python源码,面向中医临床科研与数据挖掘学习者,提供了从数据清洗到关联规则分析的完整实现。源码基于Apriori算法,配合data.xls、data_processed.xls等表格数据与说明文本,帮助读者理解中医证候数据离散化、频繁项集提取及规则评估的关键流程。算法会先分析数据中的模式与趋势,确定最优挖掘参数,再应用到数据集提取可行规则与统计信息。资源共5个文件,包含2个Excel数据文件、2个TXT说明文件和1个Python脚本,整体仅47KB,便于快速下载与本地运行调试。目前已有1349人学习,适合刚接触关联规则挖掘或希望将数据挖掘技术应用于中医药领域的人群。通过对照源码与数据,读者可掌握中医证型数据预处理、最小支持度与置信度调参思路,并迁移至自己的科研课题中。 拿到“中医证型关联规则挖掘Python源码.rar”这种资源,很多人的第一反应是先解压,找个README对着跑一遍。但如果只是把它当黑盒跑通,那这套源码的价值就废了一半。它实际解决的是一类很具体的问题:把中医病历里的辨证结果批量拿出来,用关联规则挖掘看看哪些证型经常一起出现,比如“气滞”和“血瘀”是不是像教科书说的那样常常结伴,“气虚”和“血虚”在真实数据里重叠度到底有多高。这套源码里包含数据读取、Apriori算法实现、规则筛选、结果导出和可视化,是一个完整可改的项目骨架。适合中医临床科研人员、医学信息专业的学生,也适合所有想用关联规则挖掘练手但不想从零造轮子的Python学习者。下面我把这套源码的架构、核心算法、实操流程和踩过的坑一次讲清楚。
1. 这个项目到底在解决什么问题
1.1 先理解数据:证型不是单一标签
中医辨证和很多结构化医疗数据不一样,一个患者往往同时符合多个证型。比如一个慢性胃病患者,病历里可能写着“脾气虚证兼血瘀证”,也可能同时合并“气滞证”。如果做数据分析,就不能像处理“确诊/未确诊”那样只给一个标签,而是要让一条记录承载多个证型。
这种多标签结构,正好可以转换成关联规则挖掘里最常用的事务数据:每个患者是一个事务,每个证型是一个“项”。数据整理好后长这样:
| 患者编号 | 证型集合 |
|---|---|
| 001 | {气虚证,血瘀证,气滞证} |
| 002 | {气虚证,血虚证} |
| 003 | {气滞证,血瘀证,痰湿证} |
关联规则挖掘想回答的问题就是:{气滞证}出现时,{血瘀证}出现的概率有多大?{气虚证,血瘀证}同时出现时,{痰湿证}出现的条件概率又是否明显高于整体概率?这种思路跟中医“复合病机”研究天然匹配,所以拿来分析证型共现规律非常合适。
1.2 为什么选关联规则,而不是分类或聚类
有同行问过我:证型分析用随机森林不是更高级吗?要看你手里的任务是什么。随机森林、逻辑回归这类分类模型,要求先有一个明确的预测目标,比如“是否属于血瘀证”。但探索性研究里我们往往没有明确标签,只是想看看数据里有哪些隐藏的组合规律,这时候分类模型就不太对路。
聚类倒是能做无监督探索,但聚出来的是一堆患者分组,没法直接回答“A证型出现时B证型的出现概率变化”这种带方向性的问题。关联规则的优势在于,它输出的每条结果都带有支持度、置信度和提升度三个可解释指标,能够直接描述“哪些证型组合更容易共现”,还能量化这种共现是正相关还是负相关。对于需要写成论文、申请课题、做回顾性研究的场景,这种可解释性特别重要。
1.3 方案选型:Apriori还是FP-Growth
这套源码选择的是Apriori算法。原因很直接:证型数量通常不会特别多,临床样本量一般也就几千到几万条,Apriori的多次扫描开销完全可以接受。而且Apriori的思路最直观,先找频繁项集,再生成规则,每一步都能打印中间结果,对学习者非常友好。
如果数据量特别大、项数特别多,那可以考虑换成FP-Growth,它只需要扫描两次数据库,性能好很多。但从学习和调试角度,我仍然建议先把Apriori吃透。源码里的实现是从零写的,没有直接套mlxtend库,这样你能看到候选集是怎么自连接生成的、剪枝是怎么做的、支持度计数到底循环了几层。
这里放一段核心的候选生成逻辑,直观感受一下:
from itertools import combinations def generate_candidates(prev_itemsets, k): """ 从上一轮的频繁项集生成 k 项候选集,并做 Apriori 剪枝。 prev_itemsets 的元素是 frozenset,表示频繁项集。 """ candidates = set() prev_list = list(prev_itemsets) for i in range(len(prev_list)): for j in range(i + 1, len(prev_list)): union = prev_list[i] | prev_list[j] if len(union) == k: # 剪枝:只有所有 k-1 项子集都频繁,才保留该候选 if all( frozenset(subset) in prev_itemsets for subset in combinations(union, k - 1) ): candidates.add(union) return candidates剪枝是整个Apriori减少计算量的关键。它利用的是一条先验性质:如果一个项集的子集不频繁,那这个项集本身也不可能频繁。有了这个剪枝,候选集规模会大幅下降,几千条样本下运行时间通常都在几秒到几十秒以内。
2. 源码核心模块拆解:从CSV到关联规则
2.1 数据读取与预处理
拿到原始数据后,第一步不是跑算法,而是先清洗数据。源码里最常见的输入格式是CSV,两列:patient_id、syndrome_list。syndrome_list里每个患者的证型用逗号分隔,但很现实的问题是,有些数据用中文逗号,有些用英文逗号,还有些证型列表里有空格。
读数据的函数要处理这几种乱象:
import pandas as pd def load_transactions(csv_path): df = pd.read_csv(csv_path, encoding='utf-8-sig') transactions = [] for _, row in df.iterrows(): raw = row['syndrome_list'] # 统一把中文逗号、顿号转成英文逗号再切分 if isinstance(raw, str): raw = raw.replace(',', ',').replace('、', ',') items = [item.strip() for item in raw.split(',')] else: items = [] # 去重:一个患者同一证型只保留一次 items = list(dict.fromkeys([it for it in items if it])) transactions.append(items) return transactions这里用了utf-8-sig而不是utf-8,是考虑到Excel另存的CSV经常带BOM头,不处理的话第一列列名会多个看不见的字符,后面跑起来全是坑。另外,去重这一步不能省,因为同一个证型在一条记录里出现两次,会让支持度统计出错。
2.2 Apriori算法实现要点
事务列表准备好以后,核心就是两层循环:第一层从1项集开始,扫描数据库统计每个证型出现次数,筛掉支持度低于阈值的项;第二层用上一轮的频繁项集生成候选集,再扫描数据库统计候选集支持度,循环直到没有新的频繁项集产生。
频繁项集生成的核心代码看起来是这样的:
def apriori(transactions, min_support): total = len(transactions) item_count = {} # 统计每一项的出现次数 for txn in transactions: for item in set(txn): item_count[item] = item_count.get(item, 0) + 1 # 频繁 1 项集 freq_itemsets = {} current = [] for item, cnt in item_count.items(): sup = cnt / total if sup >= min_support: freq_itemsets[frozenset([item])] = sup current.append(frozenset([item])) k = 2 while current: candidates = generate_candidates(current, k) candidate_support = {} for txn in map(set, transactions): for cand in candidates: if cand.issubset(txn): candidate_support[cand] = candidate_support.get(cand, 0) + 1 current = [] for cand, cnt in candidate_support.items(): sup = cnt / total if sup >= min_support: current.append(cand) freq_itemsets[cand] = sup k += 1 return freq_itemsets这段代码里有个容易被忽略的小细节:统计单项目出现次数时,要把每行转成set(txn)再遍历,否则如果一行数据里有重复证型,计数会虚高。我见过不少初学者在这里栽跟头,跑出来的支持度明明应该最多1.0,却出现1.2这种离谱数值,基本就是重复计数造成的。
2.3 关联规则生成与筛选
频繁项集挖掘只是前半部分,关联规则才是真正面向分析的输出。一条规则形如{气滞证} → {血瘀证},含义是“当患者属于气滞证时,也属于血瘀证的概率”。生成规则时,枚举频繁项集的非空子集作为前件,剩下的作为后件,然后计算置信度和提升度。
三个指标的计算公式是:
- 支持度:
support(A→B) = support(A ∪ B) - 置信度:
confidence(A→B) = support(A ∪ B) / support(A) - 提升度:
lift(A→B) = confidence(A→B) / support(B)
提升度是最容易被人忽略的指标。置信度高不代表规则一定有价值,因为如果B证型本身出现频率就非常高,比如所有患者里60%都有气虚证,那前件A出现时再看B的置信度可能也会很高,但这只是基线水平。提升度大于1,才说明A的出现确实让B的出现概率显著提高了。
源码里通常会给规则生成加两个筛选条件:
def generate_rules(freq_itemsets, min_conf, min_lift): rules = [] for itemset, support in freq_itemsets.items(): if len(itemset) < 2: continue antecedent = itemset - {next(iter(itemset))} consequent = itemset - antecedent conf = support / freq_itemsets[antecedent] lift = conf / freq_itemsets[consequent] if conf >= min_conf and lift >= min_lift: rules.append({ 'antecedent': ','.join(sorted(antecedent)), 'consequent': ','.join(sorted(consequent)), 'support': round(support, 4), 'confidence': round(conf, 4), 'lift': round(lift, 4) }) return rules上面的写法是简化版,实际源码里通常会枚举所有子集作为前件,得到更完整的规则列表。但无论怎么枚举,最后都要用min_conf和min_lift双层过滤,否则规则数量会爆炸到几千条,根本看不过来。
2.4 结果导出与可视化
跑出来的规则如果只在终端里打印,写论文和复盘都不方便。源码里有导出模块,把频繁项集和关联规则分别写到output/frequent_itemsets.csv和output/association_rules.csv,直接用pandas的to_csv就行。
可视化部分我建议重点看两个图。第一个是支持度-置信度散点图,以支持度为横轴、置信度为纵轴、提升度作为颜色映射,能一眼看出哪些规则既有普遍性又有强度。第二个是关联网络图,节点是证型,边是规则,边的粗细可以映射置信度,这种图放在汇报材料里很直观。
散点图的绘制代码:
import matplotlib.pyplot as plt def plot_rules(rules_df, output_path): plt.figure(figsize=(8, 6)) scatter = plt.scatter( rules_df['support'], rules_df['confidence'], c=rules_df['lift'], cmap='viridis', alpha=0.7 ) plt.colorbar(scatter, label='lift') plt.xlabel('support') plt.ylabel('confidence') plt.title('Association Rules: Support vs Confidence') plt.tight_layout() plt.savefig(output_path, dpi=150)画网络图的时候,如果规则特别多,建议先按提升度降序排序只取前30条,不然图上一团黑线,完全看不出结构。
3. 实操过程:把源码真正跑起来
3.1 解压、结构与环境准备
拿到源码.rar后,解压出来的目录大概是这样的结构:
medical_tcm_association/ ├── data/ │ └── tcm_syndrome.csv ├── scripts/ │ ├── data_preprocess.py │ ├── apriori_engine.py │ ├── rule_mining.py │ └── visualize.py ├── output/ └── requirements.txt先看requirements.txt,这个项目依赖很少,基本就是pandas、numpy、matplotlib。如果你还没装Python,建议直接装官方3.8以上的版本,安装过程中注意勾选“Add Python to PATH”,这一步能省掉后面很多环境变量问题。装好之后在项目目录下创建虚拟环境,然后用pip安装依赖:
python -m venv venv # Windows: venv\Scripts\activate # Linux/macOS: source venv/bin/activate pip install pandas numpy matplotlib如果你平时用VS Code或PyCharm,核心就一件事:让IDE选中这个虚拟环境里的Python解释器。VS Code按Ctrl+Shift+P,调出“Python: Select Interpreter”即可;PyCharm在设置里添加本地解释器就行。环境没问题后,直接运行主入口脚本。
3.2 构造一份可运行的测试数据
项目里自带的tcm_syndrome.csv是脱敏数据,但很多学习者拿到后不敢直接动,怕把原始数据跑坏。建议先复制一份,再做一套模拟数据练手。模拟数据可以故意加入一些“规律”,方便验证算法找不找得出来。
比如可以设计500份模拟病历,让“气滞证”和“血瘀证”共现概率明显偏高,让“气虚证”和“血虚证”共现概率明显偏高,其他证型随机组合。生成脚本不复杂:
import random import pandas as pd random.seed(42) base = ['气虚证', '血虚证', '阴虚证', '气滞证', '血瘀证', '痰湿证', '湿热证', '阳虚证'] def gen_syndromes(): n = random.choices([2, 3, 4], weights=[5, 3, 2])[0] return random.sample(base, n) patients = [] for pid in range(1, 501): syndromes = gen_syndromes() # 人为提高部分组合概率 if random.random() < 0.5 and '气滞证' in syndromes and '血瘀证' not in syndromes: syndromes.append('血瘀证') if random.random() < 0.4 and '气虚证' in syndromes and '血虚证' not in syndromes: syndromes.append('血虚证') patients.append([pid, ','.join(syndromes)]) pd.DataFrame( patients, columns=['patient_id', 'syndrome_list'] ).to_csv('data/tcm_syndrome.csv', index=False, encoding='utf-8-sig')这个小脚本能验证算法是否正确:如果挖掘结果里没有出现“气滞证→血瘀证”这类预期规则,那就要回头检查代码了。注意,这只是算法测试用的演示数据,不能当作真实临床结论。
3.3 参数怎么定:支持度、置信度、提升度
跑之前要先设置三个阈值,这一步直接影响结果数量。很多人一上来就随手填min_support=0.1、min_confidence=0.8,结果频繁项集为空或者只有两三条规则,又觉得算法没用。
我的建议是先用代码统计一下每个证型的出现频率分布,再拍阈值。比如先跑:
df = pd.read_csv('data/tcm_syndrome.csv', encoding='utf-8-sig') all_items = df['syndrome_list'].str.replace(',', ',').str.split(',') from collections import Counter cnt = Counter() for items in all_items: cnt.update([it.strip() for it in items if it.strip()]) print(cnt.most_common())看几个高频率证型的数量,取一个能筛掉低频噪音、但保留核心证型的数值。对证型数据来说,min_support常见区间是0.02到0.1,min_confidence常见0.5到0.7,min_lift建议从1.2开始试。不要一上来就追求高置信度,先让规则数量宽松一些,看看数据里大概有什么趋势,再逐步收紧。
3.4 结果解读与科研价值
以模拟数据跑出来的结果为例,假设输出里有这样一条规则:
| 规则 | 支持度 | 置信度 | 提升度 |
|---|---|---|---|
| 气滞证 → 血瘀证 | 0.125 | 0.67 | 1.83 |
| 气虚证 → 血虚证 | 0.14 | 0.61 | 2.05 |
| 痰湿证,气虚证 → 血瘀证 | 0.085 | 0.72 | 1.98 |
支持度0.125,说明约12.5%的样本里“气滞证”和“血瘀证”同时出现,这个组合不是小概率事件。置信度0.67,说明诊断气滞证的样本中有67%同时有血瘀证。提升度1.83,说明血瘀证在气滞证条件下的出现概率,是总体基线概率的1.83倍。这种量化结论,可以直接写进中医证候研究的分析报告里。
但记住一点,关联规则挖掘出的只是统计共现规律,不是因果关系。它能提供科研线索,比如“气滞证与血瘀证高度相关”可以继续做临床回顾性验证,但不能直接拿来指导处方用药。写论文的时候,把支持度、置信度、提升度三个指标一起呈现,比单独列一个置信度要专业得多。
4. 常见问题与排查技巧实录
4.1 中文乱码:先解决编码问题
最常见的问题是pandas读CSV后中文全是乱码,或者列名显示成“锟斤拷”。大部分情况是编码不匹配。源码默认用utf-8-sig读取,但如果你手里的CSV是从Excel里导出的老数据,可能是gbk编码。改读取参数即可:
df = pd.read_csv('data/tcm_syndrome.csv', encoding='gbk')如果不知道文件是什么编码,可以用记事本打开文件另存为,看右下角编码提示,再回到代码里替换。还有一个操蛋的点:有些CSV看着是utf-8,但里面几行脏数据用gbk编码,读取时会报UnicodeDecodeError。这时候可以给read_csv加参数encoding='utf-8', errors='ignore',先把数据读进来再看哪里断了。
4.2 支持度阈值不会选:先看项频分布
频繁项集为空,多半是min_support设得太高;规则暴多,多半是太低。别凭感觉调,先做项频统计。如果数据里有20个证型,平均每个证型出现概率才5%,那你设min_support=0.1就是在自断后路。建议把项频从高到低排序,看分布曲线在哪个位置出现拐点,把阈值设在拐点附近,既能保留高频核心证型,又能滤掉一次性的噪音数据。
4.3 Apriori跑得慢:三个优化方向
如果样本量从一万涨到十万,事务数量变大,Apriori的多次扫描就会开始吃力。这时候先做三件事:一是把transactions都转成set,判断子集时用issubset比列表遍历快很多;二是在生成候选集时严格做k-1子集剪枝,别省这一步;三是先把低频证型过滤掉,比如出现次数少于5次的证型直接删掉,因为超低频项几乎不可能进频繁项集,留着只会增加组合数量。
如果优化完还是慢,就换FP-Growth,或者直接用mlxtend库的frequent_patterns.apriori和association_rules,几行代码就能顶上。
4.4 依赖安装失败:镜像源与虚拟环境
有些环境里直接pip install pandas会卡在下载阶段,尤其是默认源访问不稳定的时候。可以用国内镜像源解决:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas numpy matplotlib装依赖时建议始终用虚拟环境,不要跟系统Python混在一起。我在一个管理混乱的服务器上踩过坑,系统Python里装了一套老版本numpy,项目里又需要新版,最后两个环境互相污染,连基本的import pandas都报错。虚拟环境能隔离这些麻烦。
4.5 规则太多太乱:加约束条件
规则数量动辄上千条时,优先按提升度降序排序,只看排名前20-50条。还可以给规则左右两侧的项数加约束,比如只保留前件不超过2项、后件只有1项的规则。后件只有1项意味着规则可以直接解读为“某几个证型组合条件下,某个证型出现概率如何”,可解释性最强。高阶规则用于发现复杂组合,但需要人工复核的精力也大得多。
4.6 同名不同称:证型名称标准化
这是最烦人的数据问题,不同来源的病历里,“气虚证”可能被写成“气虚”,也可能被写成“脾气虚”。如果直接拿去跑,程序会把它们当两个不同的项,结果自然失真。解决办法是在预处理阶段维护一个别名映射表:
alias = { '气虚': '气虚证', '脾气虚': '气虚证', '血瘀': '血瘀证', '气滞': '气滞证', } def normalize(name): return alias.get(name, name)宁可花时间在数据清洗上,也别急着调算法参数。数据里有脏东西,算法再高级也会给出误导性结论。
最后再说说我的个人体会。这类挖掘项目做多了以后,你会发现最难的部分往往不在Apriori本身,而在数据整理和结果解释。病案数据里的证型名称、合并规则、录入习惯,每一样都能让结果失真。建议你拿到源码后,先用小样本把流程跑通,再去处理全量数据。每次运行前把输出目录清空,或者保存带时间戳的结果文件,后面复盘会省很多事。多做几次对比实验,你就会发现,那些让你眼前一亮的规则,往往不是已知的经典组合,而是数据里被忽略的真实共现。
本文还有配套的精品资源,点击获取