简介:面向Python数据分析初学者与高校教学场景的实践型配套资源包,内容覆盖从数据加载、清洗、特征工程到建模评估、结果可视化的完整流程。压缩包内含约两千个文件,总大小约120MB,以代码脚本、数据集、可视化图表、说明文档及交互式演示页面等为主,便于读者对照代码逐步理解思路并复现实验结果。目前已有两百二十四人学习使用。资源中提供电商用户行为、区域房价分布、聚类结果分组等多场景完整项目,读者可结合真实数据与配套脚本,熟悉Pandas、Numpy、Scikit-learn等常用库的操作方法,掌握数据预处理、特征构造、模型选择与调优、模型评估等关键技能,系统提升数据挖掘实战能力。可作为课程设计、毕业设计或竞赛准备时的参考实现,缩短从理论到落地的距离。
1. Python 数据挖掘实践配套代码及数据:不是给你看,是给你跑的
这份压缩包拆开后,里面全是一堆 HTML 文件:南京二手房总价小于 200 万的分布图、k-means 聚类结果分组 0~4 的六张图,外加一份“原始代码.html”。第一次解压的人很容易误判它是一个看图包,实际它是《Python 数据挖掘实践》教学的配套产物——HTML 里嵌着原始代码、交互图表和分组明细,聚类跑出来的结论已经被固化成了文件。我拆完后的判断是:它适合正在学 Python 数据挖掘、想拿真实城市数据走一遍全流程的人。你能从它学到的不是单个函数,而是「pandas 清洗 → sklearn 聚类 → pyecharts 出图 → 分组落地」的完整闭环。想用 k-means 练手的,直接照着 HTML 里的代码重跑一遍,比看任何教程都管用。
2. 搭建 Python 环境并把 zip 包验证干净:从 python 安装到依赖对齐
资源里的代码不会自己跑起来,第一步永远是先把环境对齐。数据挖掘项目对环境敏感,pandas、scikit-learn 的版本差异经常导致结果和图表的细微不同,所以我不建议你直接在系统 Python 里裸装,而是单独建一个虚拟环境,让这个资源拥有一套独立依赖。
2.1 为什么先对齐 Python 和依赖版本:数据挖掘脚本最容易挂在环境上
我拆过很多配套资源,发现一个规律:代码本身没问题,翻车基本都翻在环境上。比如这段资源里如果用到 pyecharts 1.x,而你装的是 pyecharts 0.5.x,API 完全不同,HTML 图表就生成不出来。再比如 pandas 2.0 里部分字符串方法的行为和 1.x 有差异,处理“万”这种单位后缀时结果可能不一样。
所以拿到 zip 包别急着解压,先确定三件事:你的 Python 版本、pip 版本、以及项目依赖的库版本。这个资源包定位是教学配套,依赖不会很激进,用 Python 3.9~3.10 基本都能跑通。下面是这个场景下最常见的一组依赖:
| 库 | 用途 | 建议版本区间 |
|---|---|---|
| pandas | DataFrame 数据清洗与聚合 | 1.5.x ~ 2.0.x |
| numpy | 数值计算与数组操作 | 1.23.x ~ 1.26.x |
| scikit-learn | KMeans、StandardScaler、评估指标 | 1.2.x ~ 1.3.x |
| matplotlib | 静态图表绘制 | 3.7.x ~ 3.8.x |
| pyecharts | 生成可交互 HTML 图表 | 1.x(注意不是 0.5.x) |
| plotly | 备用的交互可视化方案 | 5.x |
| jupyter | 运行 .ipynb 教学代码 | 任意较新版本 |
版本区间是经验值,不是硬性要求。关键是别混装老版本 pyecharts,否则资源里那些带 dark 后缀的暗色主题页面你复现不出来。
2.2 python 安装与虚拟环境创建:一条命令建出干净运行空间
如果你还没装 Python,直接去官网下载 3.10 版本的安装包,安装时记得勾选“Add Python to PATH”。这一步经常被跳过,导致后面命令行里找不到 python。装完之后,我习惯用下面的方式创建虚拟环境:
py -3.10 -m venv .venv # windows 激活 .venv\Scripts\activate # mac / linux 激活 source .venv/bin/activate # 安装依赖 pip install pandas numpy scikit-learn matplotlib pyecharts plotly jupyter激活成功后,命令行前缀会变成(.venv),这时 pip 装的包都隔离在这个环境里,不会污染全局 Python。Windows 上如果执行 pip install 时报“由于找不到 msvcp140.dll 无法继续执行代码”,说明系统缺少 VC++ 运行库,去下载微软官方 Visual C++ Redistributable 装一遍就能解决,和 Python 本身没关系。
装完依赖后,强烈建议先跑一次导入校验,确认所有库的版本是兼容的:
import pandas as pd import numpy as np import sklearn import pyecharts print("pandas:", pd.__version__) print("numpy:", np.__version__) print("sklearn:", sklearn.__version__) print("pyecharts:", pyecharts.__version__)如果 pyecharts 打印出来是 0.x,说明装错了版本,立刻pip uninstall pyecharts后重装。这一步只要十分钟,但能省掉后面无数个“为什么我的图表是空白”的排查时间。
2.3 解压 zip 并验证内容完整:先把文件结构摸清楚
环境就绪后,再回来处理 zip 包。很多人直接用鼠标右键解压,解压完发现 HTML 文件散落一地。我一般会先用脚本看一眼压缩包内部结构,再决定解压到哪个目录:
import zipfile from pathlib import Path zip_path = Path("Python 数据挖掘实践配套代码及数据.zip") out_dir = Path("D:/dataming_practice") out_dir.mkdir(exist_ok=True) with zipfile.ZipFile(zip_path) as zf: # 先预览文件清单,确认目录结构,避免解压后文件满天飞 for info in zf.infolist(): print(info.filename, info.file_size) # 解压全部文件 zf.extractall(out_dir, pwd=None)代码里的infolist()会打印每个文件的名称和大小,方便你核对是否有损坏条目。extractall(out_dir)把全部文件解压到指定目录,out_dir 不存在时会自动创建。注意pwd=None表示压缩包没有密码,如果你的 zip 有密码,需要改为pwd=b"your_password"。
解压后,逐个用浏览器打开 HTML 文件验证。带dark后缀的通常是暗色主题页面,不带的是常规主题,内容应该一一对应。如果某个 HTML 打开是空白或者显示乱码,先别怀疑资源损坏,很可能是文件路径带中文导致浏览器编码识别错误,稍后再讲怎么处理。这一步走通,环境问题就算清零了。
3. 从 HTML 反推数据处理流水线:缺失值、量纲和特征工程的一条完整流水线
资源里核心主题是南京二手房,文件列表里出现了“总价小于 200 万的分布图”和 k-means 分组结果,说明原始数据处理流程大概率是:读取二手房数据 → 清洗缺失值和异常值 → 构造特征 → 交给聚类模型。原始代码.html 里应该能看到这段流程的源码,我按这个教学场景最常见的做法,把整条流水线拆给你看。
3.1 从 HTML 里的原始代码还原数据加载与字段字典
如果是网页版代码,你可以直接在浏览器里按 Ctrl+U 查看网页源码,把代码段复制出来另存为.py文件。以下是我在类似教学资源里最常见的加载方式:
import pandas as pd df = pd.read_csv("nanjing_ershoufang.csv", encoding="utf-8") print(df.shape) print(df.columns.tolist()) print(df.head())encoding="utf-8"是必须显式指定的。很多爬虫导出的二手房 CSV 实际是 GBK 编码,如果你不指定,pandas 会用系统默认编码去读,大概率直接报 UnicodeDecodeError。如果读取时报错,试着把编码改成gbk或gb2312。
在真实场景里,这份数据的字段一般长这样:
| 字段名 | 含义 | 清洗注意事项 |
|---|---|---|
| total_price | 总价,单位万元 | 可能带“万”字,需转数值 |
| area | 建筑面积,平方米 | 注意 0 和极端大值 |
| unit_price | 单价,元/平方米 | 可用 total_price*10000/area 交叉验证 |
| district | 区属,比如鼓楼、江宁 | 做编码或 one-hot |
| layout | 户型,比如“3室2厅1卫” | 正则拆出室、厅数量 |
| toward | 朝向 | 做类别映射,南北通透单独标记 |
| floor_level | 楼层/总层数 | 可能缺省,值缺失时考虑填充策略 |
字段名不一定要和资源里的完全一致,但清洗思路是一致的:先把文本字段转数值,再处理缺失,最后再谈建模。
3.2 缺失值、重复值与总价字段清洗:直接跑一遍给你看
二手房数据最常见的三个坑是缺失值、重复记录和单位后缀。下面是我常用的清洗套路,可以直接套用:
# 1. 去除完全重复的记录 df = df.drop_duplicates() # 2. 处理总价字段:去掉“万”字并转成 float df["total_price"] = ( df["total_price"] .astype(str) .str.replace("万", "", regex=False) .str.replace(",", "", regex=False) .astype(float) ) # 3. 删除总价或面积缺失的行 df = df.dropna(subset=["total_price", "area"]) # 4. 剔除明显异常值:总价小于10万或面积小于5平米 df = df[(df["total_price"] > 10) & (df["area"] > 5)] print(df.info())这段代码的逻辑是:先去掉完全重复的行,避免同一条房源被爬两次;再把总价从字符串转成数值。.str.replace("万", "", regex=False)里的regex=False表示按字面量替换,不用正则,性能更好且不会误伤其他字符。最后用布尔索引过滤掉明显不符合市场的异常记录。
注意第 2 步必须放在dropna之前,因为总价如果是 NaN,.astype(str)之后会变成字符串 "nan",后续操作会引入脏数据。正确顺序是:先转数值,再删缺失。
3.3 特征工程:面积单价交互项、朝向编码与价格带切分
数据清洗完之后,下一步是让特征更贴合聚类任务。这里重点做三件事:构造面积与总价的交叉特征、把户型文本拆成数值、把朝向映射成有序类别。
# 构造交叉特征:均价,单位万元/平方米,后续聚类时按需选用 df["avg_price"] = df["total_price"] / df["area"] # 从“3室2厅1卫”里拆出室和厅 df["room_num"] = df["layout"].str.extract(r"(\d+)室").astype(float) df["hall_num"] = df["layout"].str.extract(r"(\d+)厅").astype(float) # 朝向编码:南北通透给最高分,单朝向其次 toward_map = { "南北": 2, "南": 1, "东": 1, "东南": 1, "北": 0, "西": 0, "西北": 0, } df["toward_score"] = df["toward"].map(toward_map).fillna(0) # 构造价格带标签,用于后续验证聚类结果是否符合业务直觉 df["price_band"] = pd.cut( df["total_price"], bins=[0, 100, 200, 300, 500, 10000], labels=["<100", "100-200", "200-300", "300-500", "500+"], )str.extract(r"(\d+)室")用正则从户型文本里抓第一个数字,得到室的数量,抓不到就变成 NaN。fillna(0)处理未匹配到的朝向。pd.cut把连续的总价切成价格带,这一步不是为了建模,而是给后面解读 k-means 分组提供一个业务对照基准。
做特征工程时我习惯把每一步的结果打印出来验证一眼,别一口气写完再回头查。你会发现“为什么分组里出现单价 2 万但总价才 80 万”这种问题,十有八九是某个字符串没转干净。
4. k-means 聚类完整落地:从肘部法选 K 到分组 0~4 的业务解读
资源里的 HTML 文件名已经把结论告诉我们了:k-means 聚类结果分成了 5 组,编号 0 到 4。这意味着 K=5。但如果你自己动手复现,不能直接拍脑袋定 5,得有一个选 K 的过程。这一章讲清楚选型理由、参数含义和分组解读。
4.1 为什么用 k-means 而不是层次聚类或 DBSCAN:和业务诉求对齐
这个场景数据量在几千到几万条量级,特征以连续的数值型为主,业务诉求是“把南京二手房分成几个有解释力的价格段”,k-means 是最合适的选择。相比层次聚类,k-means 在大样本下计算快、内存占用可控;相比 DBSCAN,k-means 的结构更稳定,不会因为局部密度波动产生大量噪声点。
当然 k-means 也有边界:它假设簇是凸的,对量纲敏感。所以用之前必须做标准化,否则面积和总价不在一个数量级上,聚类结果会被单价或总价一个特征牵着走。这也是资源里为什么会有多个“分组”HTML 文件——同一个模型,输出被固化成多张页面,方便逐组观察。
| 特性 | k-means | 层次聚类 | DBSCAN |
|---|---|---|---|
| 适合规模 | 万级以上 | 几千条以内 | 任意规模 |
| 簇形状 | 偏凸形 | 任意但计算重 | 任意,含噪声 |
| 参数敏感性 | 主要调 K | 连接准则 | eps 和 min_samples |
| 结果可解释性 | 较好 | 好,有树状图 | 一般,噪声需单独处理 |
本资源核心用 k-means 是合理的,教学场景也最容易理解。
4.2 肘部法与轮廓系数选 K:别把 K 定成拍脑袋的数字
选 K 的常见做法是肘部法:遍历候选 K,计算每个 K 下的簇内误差平方和(inertia),找到曲线拐点。但惯性是随着 K 增大单调递减的,拐点经常不清晰,所以我习惯搭配轮廓系数一起看。以下是完整代码:
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score from sklearn.preprocessing import StandardScaler # 这里只取三个核心连续特征做聚类 features = ["total_price", "area", "avg_price"] X = df[features].dropna().copy() # 标准化:均值0方差1,避免量纲主导距离 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 候选K从2到8,分别记录惯性和轮廓系数 k_range = range(2, 9) inertia_list = [] silhouette_list = [] for k in k_range: km = KMeans(n_clusters=k, n_init=10, random_state=42) labels = km.fit_predict(X_scaled) inertia_list.append(km.inertia_) sil = silhouette_score(X_scaled, labels) silhouette_list.append(sil) print(f"K={k}, inertia={km.inertia_:.2f}, silhouette={sil:.4f}")n_init=10表示用 10 组不同的初始质心跑 10 次,取最优结果,避免落入局部最优。random_state=42固定随机种子,保证你重跑和第一次跑得到完全一致的标签。sklearn 新版本里n_init默认值已经变大,但显式写上能把意图表达清楚。
实际判断时:看 inertia 曲线,K=3 到 5 之间如果是明显的折线拐点,那个 K 优先;再看 silhouette,一般 0.25 到 0.5 之间说明聚类结构比较清晰。如果 silhouette 低于 0.2,这个特征组合本身可能不适合 k-means,要回退调整特征。
4.3 训练、分组与画像输出:从聚类结果到分组 0~4 业务解释
选好 K 之后,用 K=5 重训一次,并输出每个分组的特征均值,这是我在项目里必做的一步。只给标签不给画像,等于白做了聚类:
import numpy as np # 固定K=5,和资源里的分组0~4对齐 km = KMeans(n_clusters=5, n_init=20, random_state=42) km.fit(X_scaled) # 把标签回填到原数据 df["cluster"] = km.labels_ # 生成分组画像 profile = ( df.groupby("cluster")[["total_price", "area", "avg_price"]] .mean() .round(2) ) print(profile)跑完后,你会看到一个类似下面的画像表(数值因数据而异):
| cluster | total_price 均值 | area 均值 | avg_price 均值 | 业务定性 |
|---|---|---|---|---|
| 0 | 180 万 | 62 ㎡ | 2.9 万/㎡ | 刚需上车盘 |
| 1 | 350 万 | 105 ㎡ | 3.3 万/㎡ | 改善型 |
| 2 | 95 万 | 40 ㎡ | 2.4 万/㎡ | 老破小/小户型 |
| 3 | 680 万 | 160 ㎡ | 4.2 万/㎡ | 高端改善/学区 |
| 4 | 130 万 | 55 ㎡ | 2.3 万/㎡ | 低价刚需 |
注意:k-means 的分组编号是随机的,编号 0 不代表总价最低,只代表算法迭代时最先碰到的质心。如果你看到自己的分组 2 和其他人分组 2 含义完全不同,这是正常的,后面避坑章会讲如何重映射标签,让分组编号按业务口径统一。
5. 避坑与常见问题排查:聚类现场的五个坑还原
这部分是我在实际复现教学资源时踩过或看别人踩过的坑,每个都按现象、原因、解决的顺序讲清楚。没有玄学,全是真实操作细节。
5.1 HTML 图表打不开:双击变成空白页
现象:解压后双击“南京二手房总价小于200万的分布图.html”,浏览器打开是一片空白,控制台报错。
原因:这类 HTML 图表是 pyecharts 或 plotly 生成的,JavaScript 脚本需要从本地或其他路径加载资源。如果你的文件被移动过,相对路径失效,图表自然渲染不出来;另一种情况是浏览器插件拦截了本地脚本。
解决:把整个目录保持原样,不要单独把 HTML 抽出来放在别处。用浏览器打开时,优先用 Chrome 或 Edge,地址栏输入file:///开头的完整路径,避免双击默认浏览器造成编码识别问题。如果还是空白,右键查看网页源码,看<div>容器里是否有图表初始化代码,确认 HTML 没有损坏。
5.2 聚类结果每次跑都不一样:同一份代码两次结果不同
现象:你照着原始代码重跑了一次 k-means,发现分组结果和资源里保存的完全对不上,甚至自己连跑两次出来的分组归属也不同。
原因:KMeans 的初始质心是随机选择的,sklearn 默认n_init较小时,不同批次的结果会漂移。教学资源里的 HTML 是用固定随机种子生成的,你复现时没设置random_state,就复现不出完全一致的分组。
解决:建模时显式传入random_state=42和n_init=10以上。Python 运行前先执行import numpy as np; np.random.seed(42)统一随机源。如果仍然不一致,检查 pandas 读入数据的行序是否一致,df.sample()或洗牌操作会打乱行序,直接影响聚类结果。
5.3 总价字符串转数值后整列全是 NaN
现象:执行df["total_price"].astype(float)后,总价列变成大量 NaN,后续聚类直接报错。
原因:总价字段里混入了逗号分隔符,比如"1,050,000"或"1050万"这种混合格式,astype(float)遇到非法字符串会直接转为 NaN,而且会在整列生效。
解决:先清理再转换,按顺序执行:去逗号、去“万”字、统一转 float。推荐用pd.to_numeric(df["total_price"], errors="coerce"),errors="coerce"会把无法解析的值转成 NaN 而不是抛异常,这样你能看到哪些脏数据需要单独处理。
df["total_price_clean"] = pd.to_numeric( df["total_price"].astype(str).str.replace(",", "", regex=False), errors="coerce", )5.4 忘了标准化,聚类结果只按总价一个维度切分
现象:分组画像显示 total_price 差异很大,但 area 和 avg_price 在各组几乎没有区分度,整份结果像是“手动设阈值切总价”。
原因:总价的数值范围(几十到上千)远大于面积和均价,欧氏距离计算时总价主导了样本距离。不标准化的情况下,k-means 等价于只对总价做一维分箱。
解决:对参与聚类的所有连续特征做StandardScaler,在训练前用scaler.fit_transform(X),而不是只用原始数值。注意标准化和反标准化要成对使用:预测时用训练时的scaler.transform,描述分组时用scaler.inverse_transform还原成真实价格,否则你打印出来的分组中心是 0 均值标准化后的数值,没法直读。
5.5 标签编号不对齐:你的分组 0 不是资源里的分组 0
现象:你复现出 5 个分组,但和资源里分组 0~4 的画像对应不上,你的分组 0 总价均值最高,资源里分组 0 却是最低价组。
原因:k-means 的标签编号由初始化位置决定,同一个模型跑 10 次种子,分组编号顺序都不同。编号本身没有业务含义,说“分组 0 是刚需盘”是错误理解。
解决:训练完成后,用聚类中心的总价均值给每组分排序,把标签统一重映射到业务顺序:
# 按聚类中心的 avg_price 从小到大排序,重映射标签编号 order = np.argsort(km.cluster_centers_[:, 2]) # 第3列是avg_price label_map = {old: new for new, old in enumerate(order)} df["cluster_ordered"] = df["cluster"].map(label_map)这里的order是聚类中心按均价排序后的旧标签索引,label_map把旧标签映射成 0~4 的新编号,0 表示均价最低的一组。这样做之后,你的分组 0 就和资源里按业务口径定义的“低价刚需组”对齐了。
6. 把分组结果做成可交付结论:稳定性验证、业务标签与一键出图
聚类做完不等于项目交付,业务方要的是“哪些房源归到同一类、为什么”。我最后一步习惯做两件事:稳定性验证和业务标签落地。
先跑一遍稳定性验证,用多个随机种子重复聚类,计算两两之间标签的相似度,确认分组不是随机种子碰巧碰出来的:
from sklearn.metrics import adjusted_rand_score X_scaled = scaler.fit_transform(df[features]) labels_list = [] for seed in [0, 1, 42, 2024]: km = KMeans(n_clusters=5, n_init=20, random_state=seed).fit(X_scaled) labels_list.append(km.labels_) # 用ARI衡量不同种子下聚类结果的一致性,1表示完全一致 ari = adjusted_rand_score(labels_list[0], labels_list[2]) print(f"seed 0 vs seed 42 ARI: {ari:.4f}")ARI 在 0.9 以上说明聚类结构稳定,可以作为可靠结论交付;低于 0.7 建议重新选特征或调 K。这一步做完,再把分组标签映射成业务口径,输出最终 CSV:
df["business_label"] = df["cluster_ordered"].map({ 0: "低价小户", 1: "刚需上车", 2: "改善型", 3: "高端改善", 4: "学区/稀缺" }) df[["district", "layout", "total_price", "area", "avg_price", "cluster_ordered", "business_label"]].to_csv("nanjing_clusters.csv", index=False)最后用 pyecharts 画一张按分组着色的散点图,X 轴面积、Y 轴总价,直接输出 HTML,和资源里那些页面保持同一种交付形态:
from pyecharts.charts import Scatter from pyecharts import options as opts scatter = ( Scatter() .add_xaxis(df["area"].tolist()) .add_yaxis( "南京二手房分组", df[["total_price", "cluster_ordered"]].values.tolist(), is_selected=True, ) .set_global_opts(title_opts=opts.TitleOpts(title="南京二手房 k-means 分组")) ) scatter.render("nanjing_clusters.html")从那以后,我每次做聚类交付都强制走一遍这套流程:先标准化、再固定随机种子、输出分组画像、重映射标签、验证稳定性,最后才生成图表。这几个动作缺一个,结果就有可能在交付时被人问倒。希望帮到你,也祝你能把这套配套资源吃透,跑出自己的分组结论。
本文还有配套的精品资源,点击获取