简介:这是一份面向数据挖掘与粗糙集理论学习者的Python实现资源,聚焦粗糙集属性约简算法,并配套图形化交互界面,可直观展示约简过程与前后属性变化,适合课程实验、毕业设计或算法入门场景,也能帮助研究者快速验证算法思路。压缩包共16个文件,涵盖4个Python脚本(分别负责程序入口、数据输入与主界面展示)、3个编译缓存文件、4个工程配置XML、2个文本说明及1个CSV样例数据,整体仅14KB,轻量且结构清晰,无需复杂依赖即可运行。该资源已被1214人学习下载,适合需要动手实践粗糙集算法的数据挖掘学习者参考使用。使用时只需运行主程序文件即可启动,内置样例数据与平台使用说明,便于快速验证算法效果;同时保留项目配置与缓存目录,可辅助理解工程实现细节。需注意必须在data_dimension_reduction目录下运行,避免路径读取异常。
1. 粗糙集简约算法不是黑匣子:一份数据挖掘入门的完整资源
最常见的诉求是:手里一张几万行的表格,几十个条件属性,想知道到底哪几列真正决定了最后的分类结果。粗糙集简约算法干的就是这件事——不依赖概率分布假设,直接从决策表算出哪些属性冗余,留下一个维度更低、决策能力不掉的属性子集。这份“粗糙集简约算法+图形可视化”资源,把约简算法和可视化脚本绑在一起:前半段能算出核属性与约简结果,后半段把依赖度曲线、属性贡献条形图和降维散点图画出来。适合数据挖掘课设、论文预研、做系统原型的从业者直接上手,也适合想把粗糙集从公式落成代码的人。
2. 先搞清楚粗糙集在算什么:从等价类到核属性,约简的判定标准只有一条
2.1 粗糙集的对象是决策表:等价类决定了你能看到什么
粗糙集理论是 Pawlak 在 1982 年提出的,它不要求数据满足正态分布,也不需要先验概率,核心对象就是一张决策表。决策表的行是样本,列分两类:条件属性描述对象特征,决策属性给出类别。比如订单数据里,金额、频次、渠道是条件属性,最终标记“正常/异常”是决策属性。资源里的算法和可视化全部围绕这种表格展开,跑任何实验前先把表结构确认好。
等价类是理解粗糙集的入口。给定一组属性,所有在属性上取值完全相同的样本被分进同一个等价类。以下面这张六条样本的决策表为例,按属性 a 划分,得到两个等价类 {U1, U2} 和 {U3, U4}。每个等价类内部,决策属性 d 的取值完全一致,说明只看 a 就能确定分类。
表 1 一个极端简化的决策表示例
| 样本 | a | b | d |
|---|---|---|---|
| U1 | 0 | 0 | 0 |
| U2 | 0 | 1 | 0 |
| U3 | 1 | 0 | 1 |
| U4 | 1 | 1 | 1 |
再看属性 b,按 b 划分得到 {U1, U3} 和 {U2, U4},第一个类里 d 是 0 和 1,第二个类里 d 也是 0 和 1,每个等价类内部都不一致。也就是说,单看 b 无法确定样本类别,b 是冗余属性的候选对象。粗糙集把“某个等价类内决策一致”的样本归入正域,正域大小直接决定一个属性子集有多强的判别力。
2.2 约简的判定标准:依赖度不降,且没有更小的属性子集
正域样本数除以总样本数,就是依赖度 γ。全集属性 {a, b} 的依赖度是 1,因为每个样本单独成类,类内决策必然一致。只看 {a} 时,正域包含全部四条样本,依赖度同样是 1;只看 {b} 时,正域为空,依赖度是 0。这个对比说明 a 保留了完整决策能力,b 拿掉后依赖度不掉。
约简的定义由此而来:属性子集 B 是约简,当且仅当 γ_B(D) = γ_C(D),并且 B 的任何真子集的依赖度都严格小于 γ_B(D)。用大白话说,一是决策能力不能掉,二是不能再少任何一个属性。核属性是另一个关键概念:所有约简的交集。只要去掉某个属性后依赖度下降,这个属性就在核里。上表去掉 b 后 γ 仍是 1,所以 b 不在核里;去掉 a 后 γ 变成 0,所以 a 是核属性。
实际数据集里,核可能为空,也可能包含多个属性。求核的作用是给后续贪心搜索一个可靠的起点,避免从空集盲猜。资源里 compute_core 用的就是逐个删除再比较依赖度的方法,小数据量上直观且不容易出错。
2.3 资源包里的三个部分:算法脚本、样例数据、可视化模块
下载这份资源后,通常会看到三块内容配合使用。算法脚本负责离散化、依赖度计算、核属性与贪心约简,这是整个流程的主干;样例数据是一张已经离散化或半离散化的决策表,用来验证算法是否跑通;可视化模块读取同一份数据和约简结果,生成依赖度曲线、属性贡献条形图、PCA 散点图和对比表格。
表 2 资源包的典型构成与接口
| 模块 | 作用 | 主要接口 |
|---|---|---|
| 约简算法 | 离散化、正域、依赖度、核、贪心约简 | dependency_degree()、compute_core()、greedy_reduct() |
| 样例数据 | 验证流程是否跑通 | data.csv,条件属性列 + 决策属性列 |
| 可视化模块 | 把约简过程变成可解释图形 | plot_gamma_curve()、plot_attr_contribution()、plot_compare_scatter() |
| 参数配置 | 固定分箱数、随机种子、测试集比例 | config.py 或函数入参 |
我的建议是先把样例数据跑一遍,确认输出的是核属性列表和一个约简结果,再替换成自己的数据。替换时最容易出问题的是列名和数据类型,资源里所有函数都要求传入条件属性列名组成的列表和决策属性列名,保持一致就不会翻车。
3. 把约简算法跑通:离散化预处理与贪心属性约简代码实现
3.1 数据预处理:连续属性离散化的三种做法与选择理由
粗糙集算法天然要求属性是离散的。连续数值属性直接参与 groupby,几乎每个样本都会成为独立等价类,依赖度虚高到 1,约简结果失去意义。所以第一步是把连续列切成离散区间,常见做法有三种:等宽分箱、等频分箱、按业务阈值手动切分。等宽分箱实现最简单,但数据分布不均匀时某个箱可能空掉;等频分箱保证每箱样本量接近,推荐优先使用;业务阈值的可解释性最好,适合有明确规则的场景。
下面这段代码加了判断:如果属性唯一值数量超过阈值才离散化,否则直接转成整数,避免把已经离散的类别列再切一刀。
import pandas as pd def auto_discretize(df, cols, bins=5, unique_threshold=10): df = df.copy() for col in cols: if df[col].nunique() > unique_threshold: df[col] = pd.cut( df[col], bins=bins, labels=False, duplicates='drop' ) else: df[col] = df[col].astype(int) return df逻辑说明:pd.cut 把连续值切成 bins 个区间,返回每个样本所在区间的编号,labels=False 表示不要区间标签,直接给整数编号,方便后面参与 groupby。duplicates='drop' 处理边界值重复导致空箱的情况。unique_threshold 的作用是保护已经离散的列,比如性别、星期几这类取值很少的字段,不应当做连续属性处理。
参数说明:bins 是分箱数量,默认 5,数值越大等价类分得越细,正域可能变大,但约简更不稳定;unique_threshold 是离散/连续的判断阈值,超过它就认定是连续列,一般设在 10~20 之间。对不平衡分布,我一般会改用 pd.qcut 做等频分箱,再把分箱边界打印出来,记录在实验配置里,方便别人复现。
3.2 正域与依赖度:粗糙集算法里最核心的一段代码
正域计算是粗糙集算法的心脏。给定一个属性子集,先按这些属性分组得到等价类,然后检查每个等价类里的决策属性是否完全一致,一致的类加入正域。依赖度就是正域样本数除以总样本数。
def equivalence_classes(df, attrs): groups = df.groupby(list(attrs)).groups return [list(v) for v in groups.values()] def positive_region(df, cond_attrs, dec_attr): pos_indices = [] for idx_list in equivalence_classes(df, cond_attrs): dec_values = df.iloc[idx_list][dec_attr].unique() if len(dec_values) == 1: pos_indices.extend(idx_list) return set(pos_indices) def dependency_degree(df, cond_attrs, dec_attr): pos = positive_region(df, cond_attrs, dec_attr) return len(pos) / len(df)逻辑说明:equivalence_classes 用 pandas 的 groupby 按指定属性列分组,得到每组样本索引;positive_region 遍历每个等价类,unique() 只保留决策值去重结果,长度为 1 说明该类内决策一致,整个类并入正域。最终 dependency_degree 返回 0~1 之间的依赖度。这三个函数被核属性计算和贪心约简反复调用。
参数说明:attrs 是条件属性列表,dec_attr 是决策属性列名。这里有个性能关键点:equivalence_classes 每次调用都会重新 groupby,属性子集一变就要重算。数据量上万后,这个开销会被放得很明显,第 5 章会专门讲怎么优化。小数据集上这段代码足够直接,也最容易验证每一步的输出。
3.3 核属性与贪心约简:从核出发,让结果更稳定
计算核属性的思路是逐个尝试删掉一个属性,看依赖度是否下降。删掉后依赖度掉了,说明该属性承载了其他属性无法替代的决策信息,属于核属性。贪心约简从核开始,不断挑选一个剩余属性加入当前集合,要求它带来的依赖度增量最大,直到依赖度追平全属性依赖度。
def compute_core(df, cond_attrs, dec_attr): full_gamma = dependency_degree(df, cond_attrs, dec_attr) core = [] for attr in cond_attrs: remaining = [x for x in cond_attrs if x != attr] current_gamma = dependency_degree(df, remaining, dec_attr) if abs(current_gamma - full_gamma) > 1e-9: core.append(attr) return core def greedy_reduct_with_trace(df, cond_attrs, dec_attr): target = dependency_degree(df, cond_attrs, dec_attr) current = compute_core(df, cond_attrs, dec_attr) trace = list(current) remaining = [a for a in cond_attrs if a not in current] while dependency_degree(df, current, dec_attr) < target - 1e-9: best_attr, best_gain = None, -1.0 for attr in remaining: gamma = dependency_degree(df, current + [attr], dec_attr) gain = gamma - dependency_degree(df, current, dec_attr) if gain > best_gain: best_gain, best_attr = gain, attr if best_attr is None: break current.append(best_attr) trace.append(best_attr) remaining.remove(best_attr) return current, trace逻辑说明:compute_core 对每个属性执行一次删除再比较,1e-9 是浮点误差容差,确保依赖度差值为 0 时不做误判;greedy_reduct_with_trace 记录 trace,保存属性被加入的顺序。trace 在后续可视化里直接可用,画依赖度曲线时不需要重新推演一遍贪心过程。
参数说明:cond_attrs 顺序不影响贪心结果吗?影响。当两个属性带来相同增益时,先遍历到谁就选谁,这会让约简结果在属性增益相同的情况下依赖列顺序。想要可复现,就把 cond_attrs 排序后传入。从核出发而不是从空集出发,能减少这种随机性,得到的约简也更容易被解释,因为核属性是必须保留的部分。
3.4 参数说明:分箱数、缺失值与数据规模怎么定
跑通这套代码后,真正要花心思的是参数。分箱数 bins 直接改变等价类边界,进而改变正域大小,最好多试 3、5、8 三档,观察核属性是否稳定。缺失值没有默认机制,groupby 会把 NaN 当成一个独立类别,导致正域计算失真,我一般先删除缺失行,再跑离散化。
数据规模决定能不能用朴素版本。几百行时 compute_core 的逐个删除没有压力;几万行时每次依赖度计算都要全表 groupby,时间和内存都会暴涨。常见做法是先用去重后的决策表计算,重复行合并后决策表规模变小,正域和依赖度理论上不受影响。资源里的样例数据规模不大,新手先用它验证流程是对的,别一上来就丢几百万行进去。
提示:如果离散化后发现某个属性几乎所有样本都落在同一个箱子里,这个属性基本没有区分力,后续约简大概率会被剔除,可以先人工确认它是不是真的需要参与计算。
4. 让约简过程可见:图形可视化的四种视图与 Python 实现
4.1 可视化要看什么:四张图对应四个问题
约简算法跑完只给一个属性列表,很难说服别人“为什么保留这几个”。图形可视化的作用是把过程变成可以检查的证据链。依赖度曲线回答“属性加入顺序是否合理”,曲线上升快说明前面加入的属性重要;属性贡献条形图回答“每个属性单打独斗的能力”,适合做汇报素材;PCA 散点图回答“约简前后类别分得开吗”,用于直观感受信息损失;对比表回答“属性数量降了多少、准确率掉没掉”,是最终结论数据。
表 3 可视化视图与对应问题
| 视图 | 回答的问题 | 产出物 |
|---|---|---|
| 依赖度曲线 | 属性加入顺序的价值增长 | gamma_curve.png |
| 属性贡献条形图 | 单个属性对正域的贡献 | attr_contribution.png |
| PCA 散点图 | 约简前后类别分布差异 | reduct_compare.png |
| 指标对比表 | 属性数、依赖度、准确率变化 | reduct_compare.csv |
4.2 依赖度曲线与属性贡献条形图的实现
依赖度曲线需要传入贪心过程的 trace。横轴是已经加入的属性个数,纵轴是当前属性子集的依赖度,每加入一个属性就记录一个点。属性贡献条形图则计算每个属性单独放入空集时的依赖度,并按贡献大小排序。
import matplotlib.pyplot as plt def plot_gamma_curve(df, trace, dec_attr): gammas = [] selected = [] for attr in trace: selected.append(attr) gammas.append(dependency_degree(df, selected, dec_attr)) plt.figure(figsize=(6, 4)) plt.plot(range(1, len(trace) + 1), gammas, marker='o') plt.xlabel('已加入属性数') plt.ylabel('依赖度 γ') plt.title('属性约简过程中的依赖度变化') plt.ylim(0, 1.05) plt.grid(True) plt.tight_layout() plt.savefig('gamma_curve.png', dpi=150) def plot_attr_contribution(df, cond_attrs, dec_attr): items = [] for attr in cond_attrs: gamma = dependency_degree(df, [attr], dec_attr) items.append((attr, gamma)) items.sort(key=lambda x: x[1], reverse=True) names = [x[0] for x in items] values = [x[1] for x in items] plt.figure(figsize=(6, 4)) plt.barh(names, values, color='steelblue') plt.xlabel('单属性依赖度') plt.title('每个条件属性的独立贡献') plt.tight_layout() plt.savefig('attr_contribution.png', dpi=150)逻辑说明:plot_gamma_curve 把 trace 里的属性逐个累计,调用 dependency_degree 计算累计依赖度,marker='o' 让每个点可见。plot_attr_contribution 用单属性依赖度排座次,这里要特别注意,单属性依赖度高不等于它一定在约简里,因为约简考虑的是属性组合后的冗余关系。
参数说明:dpi=150 满足论文插图的基本清晰度要求;ylim 上限留到 1.05 避免依赖度等于 1 时曲线顶到图框。如果 trace 为空,曲线图只有一个空坐标,这种情况出现在决策表本身没有属性时,实际使用中不会发生。属性排序用 reverse=True 让贡献大的属性排在图上最上面,符合阅读习惯。
4.3 PCA 散点图:约简前后类别分布怎么对比
PCA 散点图把决策表映射到二维平面。离散化后的属性可以直接进入 PCA,但类别型属性需要先转 one-hot,否则 PCA 会把类别大小的数值关系当作真实的线性关系。对比时把全属性约简后放在两张子图里,类别用决策属性着色,观察两个图中类别点的重叠程度。
from sklearn.decomposition import PCA def plot_compare_scatter(df, cond_attrs, reduct, dec_attr): fig, axes = plt.subplots(1, 2, figsize=(10, 4)) for ax, attrs, title in zip(axes, [cond_attrs, reduct], ['全属性', '约简后']): data = pd.get_dummies(df[attrs].astype(str)) coords = PCA(n_components=2, random_state=42).fit_transform(data) sc = ax.scatter(coords[:, 0], coords[:, 1], c=df[dec_attr], cmap='viridis', s=18, alpha=0.7) ax.set_title(title) ax.set_xlabel('PC1') ax.set_ylabel('PC2') fig.colorbar(sc, ax=axes) plt.tight_layout() plt.savefig('reduct_compare.png', dpi=150)逻辑说明:zip 同时遍历两张子图和两个属性配置,astype(str) 先把所有取值统一成字符串,避免 pandas 类型差异引发 get_dummies 的遗漏。PCA 用固定 random_state=42 保证每次结果一致,这在对比实验里是必须的,否则重跑一次图就变一个样。
参数说明:n_components=2 选择两个主成分,解释方差比例低是正常的,离散属性和 one-hot 展开后维度大量增加,前两个主成分只能保留一部分信息。散点图的重点不是精确坐标,而是约简前后类别点是否仍然相对分开。s=18 和 alpha=0.7 是为了缓解样本重叠造成的视觉遮挡,样本上万时可以再调小点。
5. 避坑指南:粗糙集约简最容易翻车的四个位置
5.1 离散化分箱一变,约简结果全变
现象:同一份数据,等宽分箱 bins=5 时约简结果是 {a, b, c},换到 bins=8 后变成 {a, b, c, e},核属性也跟着变了。初次跑的人往往以为算法不稳定,其实问题出在预处理。
原因:粗糙集的等价类完全由离散取值决定。分箱边界一旦变化,跨越旧边界的样本就换了邻居,原本决策一致的等价类可能变成不一致,正域随之缩小。等宽分箱对离群点尤其敏感,一个极端值就能把一大段样本挤进同一个箱子,边界不合理时无关属性也可能被误判为重要属性。
解决:先把每个连续属性的分布画出来,再用等频分箱或业务阈值替换等宽分箱。固定 bins 后把分箱边界打印出来存成配置文件,不要每次都手动改。我在 auto_discretize 里用 unique_threshold 防止已离散字段被二次切分,这个习惯能避开大部分这类问题。
5.2 数据上万,正域计算慢到怀疑人生
现象:小数据集跑完约简用不了一秒,换成两万行的表,compute_core 跑了几分钟还没出结果,内存占用也一路上涨。
原因:依赖度计算每次都要对当前属性集合重新 groupby,compute_core 一次性调用几十次 dependency_degree,贪心循环再叠加多次。groupby 本身的成本随数据规模线性增长,Python 循环再把这份成本放大。
解决:先去重,决策表里条件属性和决策属性完全相同的行合并成一条,正域不会因此改变。然后用 frozenset 做缓存键,把算过的属性子集依赖度存下来,避免重复计算。最后在能容忍的前提下限制候选属性数量,或者对连续属性离散化后只保留样本量足够大的等价类。两万行数据跑不动时,第一步去重通常就能缓解一大半。
5.3 核属性与约简结果对不上
现象:算法算出来的约简里没有核属性,或者核属性列表和某个约简交集对不齐。不少人以为代码写错了,翻半天最后发现是算法策略不同。
原因:核是“所有约简的交集”,但贪心约简从空集开始搜时,如果初始位置偏离核,很可能走出一个不包含核的可行解。只有当多个属性共同支撑决策信息时,逐个删除单个属性可能看不出依赖度变化,核属性被漏判的情况也会出现。
解决:代码里让 compute_core 先跑一遍,再让 greedy_reduct_with_trace 从核出发初始化 current,这样贪心结果天然包含核。如果资源实现是从空集开始的版本,跑完后手动比一次核和结果,发现不含核就换成从核启动。核属性和约简结果本来就不是“必须相等”的关系,别拿它们直接对比。
5.4 可视化图形看不出效果好坏
现象:PCA 散点图画出来,全属性和约简后的图看起来都挤成一团,颜色混在一起,完全不知道约简到底有没有保留信息。
原因:只画了最终状态,缺少对照和量化指标。PCA 只保留前两个主成分,原本就被压缩的大部分方差根本显示不出来,样本多时散点重叠严重,散点图只能用于辅助判断,不能单独当证据。
解决:把依赖度曲线、属性贡献条形图、PCA 对比图和指标表四件套一起输出。指标表列清楚属性数、正域大小、依赖度、分类准确率,汇报时先给指标表再给散点图,别人就不会说约简效果“看着没区别”了。散点图里把 alpha 调低,只保留轮廓也有帮助。
6. 把约简结果验证一遍:留出法对比与可视化报告输出
6.1 留出法对比:全属性与约简属性在同一条数据上的公平比拼
约简只是降低了属性维度,真正的价值要看分类模型能不能保住准确率。用同一份训练集和测试集分别跑全属性和约简属性,才是一组公平对比。关键点有两个:train_test_split 必须指定同一个 random_state,确保两边拿到的训练测试样本完全一致;决策树模型也用同一个 random_state,排除模型随机性对结果的干扰。
from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score def compare_full_reduct(df, cond_attrs, reduct, dec_attr, test_size=0.3): y = df[dec_attr] X_full = pd.get_dummies(df[cond_attrs].astype(str)) X_reduct = pd.get_dummies(df[reduct].astype(str)) X_tr, X_te, y_tr, y_te = train_test_split( X_full, y, test_size=test_size, random_state=42 ) X_red_tr = X_te # 占位,实际写法见下行 X1_tr, X1_te, y1_tr, y1_te = train_test_split( X_full, y, test_size=test_size, random_state=42 ) X2_tr, X2_te, y2_tr, y2_te = train_test_split( X_reduct, y, test_size=test_size, random_state=42 ) model_full = DecisionTreeClassifier(max_depth=5, random_state=42).fit(X1_tr, y1_tr) model_reduct = DecisionTreeClassifier(max_depth=5, random_state=42).fit(X2_tr, y2_tr) acc_full = accuracy_score(y1_te, model_full.predict(X1_te)) acc_reduct = accuracy_score(y2_te, model_reduct.predict(X2_te)) return acc_full, acc_reduct逻辑说明:全属性特征矩阵和约简属性特征矩阵分别做 one-hot,再分别划分数据集。由于 random_state 都是 42,两次划分在样本层面是对齐的,只是特征维度不同。max_depth=5 限制树深,避免过拟合把准确率虚高,这一项在对比时必须固定。
参数说明:test_size=0.3 表示三成样本做测试,数据量小可以提到 0.2;max_depth 可以试试 3~7 三档,选结果差异最小的档位。要注意两个模型准确率差几个点以内才算可接受,如果约简后准确率下降超过 5%,优先检查离散化参数是否合理。
6.2 把结果归档成一张表:汇报和复现都少踩坑
实验做完了,最怕换台机器重跑一遍结果对不上。我会把关键中间产物全部保存下来:分箱边界、核属性列表、约简结果、依赖度曲线、PCA 图、分类对比 CSV。CSV 里固定放四列,属性数、正域大小、依赖度、准确率,全属性和约简后各一行,这样别人一眼就能看到降维换来了什么代价。
result_rows = [ {"属性数": len(cond_attrs), "正域大小": len(positive_region(df, cond_attrs, dec_attr)), "依赖度": dependency_degree(df, cond_attrs, dec_attr), "准确率": acc_full}, {"属性数": len(reduct), "正域大小": len(positive_region(df, reduct, dec_attr)), "依赖度": dependency_degree(df, reduct, dec_attr), "准确率": acc_reduct}, ] import pandas as pd pd.DataFrame(result_rows).to_csv("reduct_compare.csv", index=False)这套归档方式看着简单,实际帮我避免了无数次重跑。从那以后我每次跑粗糙集约简,都强制走一遍固定流程:先固定离散化参数,再算核和约简,然后跑对比分类,最后把四张图加一张 CSV 存进同一个实验目录。中间任何一步觉得玄学,就回去看中间产物。希望帮到你。
本文还有配套的精品资源,点击获取