简介:这份实训数据包面向正在学习Python数据分析与挖掘的在校学生与转行自学者,围绕数据清洗、探索性分析、特征工程到机器学习建模的完整链路提供配套练习素材,帮助读者在真实数据集上巩固课堂知识、完成课程实验或毕业设计。压缩包共17个文件,约112.9MB,以csv数据表为主,辅以xlsx、xls表格和sql建表脚本,覆盖用户信息、消费记录、商品销售、访问日志等多类业务场景,可直接用于Pandas读取、可视化绘图与Scikit-Learn建模练习。目前已有395人学习下载。资源按章节组织,从基础语法与数据导入导出,到缺失值处理、描述性统计、特征选择与编码,再到线性回归、决策树、聚类及神经网络等模型训练评估,形成由浅入深的学习路径,适合边学边练、逐步积累数据驱动决策的实战经验。
1. 拿到一个实训数据压缩包,先别急着解压
很多人拿到实训数据.zip这类文件,第一反应是双击解压、打开 Jupyter、pd.read_csv一把梭,然后被编码错误、路径错乱、字段含义不明三连击打回原形。我带过几届实训,见过太多人卡在“数据在哪、长什么样、字段啥意思”这一步,代码还没写几行,时间已经过去一半。Python 数据分析与挖掘实战的核心从来不是模型多花哨,而是你能不能把一个来路不明的压缩包,变成一张干净、可解释、能喂给算法或可视化工具的表。这篇笔记就围绕这个压缩包展开:怎么在本地把环境配好、怎么把数据读进来、怎么判断它适合做描述性分析还是分类聚类、以及那些只有踩过才知道的坑。适合刚学完 Python 基础语法、准备做第一个完整数据分析项目的人,也适合需要快速复现一套实训流程的助教或自学者。下面所有操作都基于你本地已经有一个实训数据.zip,内容未知,我们一步步把它拆开看。
2. 环境与数据入口:从 python 安装到第一次成功读取
2.1 选 Anaconda 还是裸 python 安装教程里的方案
网上搜“python安装教程”会出来一堆让你去官网下 exe 再配 PATH 的流程,但做数据分析与挖掘实战,我一般直接推荐 Anaconda 或 Miniconda。原因很简单:pandas、numpy、scikit-learn、matplotlib这些包在 Windows 上裸装经常卡在编译依赖,而 conda 能一次性把二进制包和解释器版本对齐。如果你已经装了 python,也不想再下几个 G 的发行版,那就用venv加 pip,但务必把 pip 源换成国内镜像,否则装scikit-learn时你会以为电脑死机了。
先确认你当前环境里有没有 Python,以及版本是否在 3.8 以上。打开终端或 PowerShell:
python --version # 如果输出 Python 3.8.x 及以上,继续;如果提示找不到命令,先去装 Miniconda接着建一个专门用于这次实训的虚拟环境,避免和你系统里其他项目的包版本打架:
conda create -n shixun python=3.10 -y conda activate shixun # 如果你用 venv,则换成 python -m venv shixun && source shixun/bin/activate(Windows 用 shixun\Scripts\activate)环境激活后,一次性把数据分析四件套和挖掘常用库装好:
pip install pandas numpy matplotlib scikit-learn openpyxl xlrd -i https://pypi.tuna.tsinghua.edu.cn/simple这里openpyxl和xlrd是为了应对压缩包里可能出现的 Excel 文件,scikit-learn覆盖大部分基础挖掘算法。参数-i指定镜像源,不写也能装,但速度看运气。装完后用一行命令验证:
import pandas as pd, numpy as np, sklearn print(pd.__version__, np.__version__, sklearn.__version__)只要不报ModuleNotFoundError,环境就算立住了。这一步的坑在于:很多人装完包后在错误的解释器里跑代码,比如 VSCode 右下角选的还是系统 Python,结果import pandas失败。解决办法是 VSCode 里按Ctrl+Shift+P,输入Python: Select Interpreter,选中你刚建的shixun环境。
2.2 解压前先看压缩包结构,别直接 extractall
拿到实训数据.zip,不要写 Python 脚本去解压,先用系统命令看一眼里面有什么。Windows 上用tar -tf也能列 zip 内容(Win10 以上自带 tar),Linux/macOS 直接用unzip -l:
unzip -l 实训数据.zip # 输出会列出所有文件名和目录层级,注意看有没有中文文件名、有没有 __MACOSX 这种垃圾目录这一步能帮你判断三件事:数据是 CSV 还是 Excel 还是 JSON;有没有嵌套文件夹;有没有多个版本的文件(比如train.csv和train_fixed.csv)。我见过最坑的一个压缩包,里面套了三层目录,每层都有一个同名 CSV,内容还不一样。如果你直接extractall再glob,很可能读到错的那个。
确认结构后,用 Python 解压到指定目录,并统一转成 UTF-8 文件名,避免后续路径里带中文或空格导致pd.read_csv报FileNotFoundError:
import zipfile, os, shutil zip_path = "实训数据.zip" extract_dir = "shixun_data" # 如果目录已存在,先清掉,保证每次都是干净解压 if os.path.exists(extract_dir): shutil.rmtree(extract_dir) os.makedirs(extract_dir) with zipfile.ZipFile(zip_path, "r") as z: # 过滤掉 __MACOSX 和隐藏文件 members = [m for m in z.namelist() if not m.startswith("__MACOSX") and not m.startswith(".")] z.extractall(extract_dir, members=members) # 打印解压后的文件树,方便确认 for root, dirs, files in os.walk(extract_dir): level = root.replace(extract_dir, "").count(os.sep) indent = " " * 2 * level print(f"{indent}{os.path.basename(root)}/") for f in files: print(f"{indent} {f}")逻辑说明:namelist()拿到压缩包内所有条目,过滤掉 macOS 自动生成的元数据目录;extractall的members参数只解压我们筛选后的文件。参数extract_dir你可以改成任何英文路径,但别用中文,否则某些库读路径时会出玄学问题。跑完这段,你就能看到数据到底长什么样,再决定下一步用read_csv还是read_excel。
2.3 第一次读取:用 nrows 和 encoding 试探,别全量硬读
假设文件树显示有一个data.csv,路径是shixun_data/data.csv。直接pd.read_csv可能会遇到编码错误或分隔符不对。我的习惯是先读前 5 行探路:
import pandas as pd file_path = "shixun_data/data.csv" # 先试 utf-8,失败再换 gbk,这是中文数据最常见的两种编码 try: df_head = pd.read_csv(file_path, nrows=5, encoding="utf-8") except UnicodeDecodeError: df_head = pd.read_csv(file_path, nrows=5, encoding="gbk") print(df_head) print("列名:", df_head.columns.tolist()) print("形状(前5行):", df_head.shape)nrows=5只读前 5 行,速度快,不会因为文件几个 G 而卡死。encoding参数先 utf-8 后 gbk 是血泪经验,很多从 Excel 导出的 CSV 默认 gbk,你硬用 utf-8 读会直接抛异常。如果列名里带Unnamed: 0,说明原始文件有索引列,读全量时加index_col=0去掉。确认列名和分隔符正常后,再读全量:
df = pd.read_csv(file_path, encoding="utf-8") # 或 gbk print(df.info()) print(df.describe(include="all"))info()看每列的非空数量和 dtype,describe(include="all")看数值列统计和类别列频次。这两条命令能让你在 30 秒内判断数据质量:有没有大量缺失、数值列是不是被读成了 object、类别列有没有异常值。到这里,数据入口就算打通了。
3. 数据清洗与特征初探:把脏表变成能挖的表
3.1 缺失值、重复值和异常值的处理顺序
拿到df之后,别急着画图或跑模型。先处理三类脏数据:缺失、重复、异常。顺序很重要,我一般先删完全重复的行,再处理缺失,最后处理异常值。因为重复行会干扰缺失统计,而异常值可能在缺失填充后被掩盖。
# 1. 删完全重复的行 before = df.shape[0] df = df.drop_duplicates() print(f"删除重复行:{before - df.shape[0]} 条") # 2. 查看缺失情况 missing = df.isnull().sum() missing_pct = (missing / len(df) * 100).round(2) missing_df = pd.DataFrame({"缺失数": missing, "缺失比例%": missing_pct}) print(missing_df[missing_df["缺失数"] > 0].sort_values("缺失比例%", ascending=False))缺失处理策略看比例:低于 5% 的数值列直接fillna(df[col].median()),类别列用众数;5% 到 30% 之间的,考虑用模型预测填充或单独标记为“未知”;超过 30% 的列,除非业务上极其重要,否则直接drop。这里没有绝对标准,但实训数据通常缺失不会太夸张,中位数填充足够。
异常值用 IQR 法快速筛:
def iqr_outlier_bounds(series): q1, q3 = series.quantile(0.25), series.quantile(0.75) iqr = q3 - q1 return q1 - 1.5 * iqr, q3 + 1.5 * iqr num_cols = df.select_dtypes(include=["number"]).columns for col in num_cols: low, high = iqr_outlier_bounds(df[col].dropna()) outliers = df[(df[col] < low) | (df[col] > high)] if len(outliers) > 0: print(f"{col}: {len(outliers)} 个异常值,范围 [{low:.2f}, {high:.2f}]")逻辑说明:IQR 法不假设正态分布,对偏态数据也稳。参数1.5是经典系数,想更宽松可以改 3.0。发现异常值后不要无脑删,先看是不是录入错误(比如年龄 200 岁),是则修正或删除;如果是真实极端值(比如高收入人群),保留并考虑做对数变换。
3.2 用 pandas 做描述性分析:groupby 和 pivot_table 的取舍
清洗完,先做描述性分析。很多人只会df.describe(),但实训数据往往需要按类别拆分看分布。groupby适合做聚合统计,pivot_table适合做交叉表。举个例子,假设数据里有城市和销售额两列:
# groupby:按城市算销售额均值和总和 city_stats = df.groupby("城市")["销售额"].agg(["mean", "sum", "count"]).reset_index() city_stats.columns = ["城市", "销售额均值", "销售额总和", "样本数"] print(city_stats.sort_values("销售额总和", ascending=False).head(10)) # pivot_table:城市 × 产品类别的销售额交叉表 pivot = pd.pivot_table(df, values="销售额", index="城市", columns="产品类别", aggfunc="sum", fill_value=0) print(pivot.head())groupby的agg可以一次传多个函数,reset_index把分组键变回列,方便后续导出。pivot_table的fill_value=0把缺失组合填 0,避免 NaN 干扰可视化。选哪个取决于你要回答的问题:如果只是“每个城市表现如何”,用 groupby;如果要看“城市和产品类别的交互”,用 pivot_table。这一步的输出可以直接喂给 matplotlib 画柱状图或热力图,但本文不展开画图,重点在数据本身。
3.3 特征工程:从原始列里榨出可用变量
实训数据挖掘部分通常需要你构造特征。常见操作:日期列拆成年月日、类别列做 one-hot、数值列做分箱。以日期为例:
# 假设有一列 "交易时间",格式是字符串 df["交易时间"] = pd.to_datetime(df["交易时间"], errors="coerce") df["年"] = df["交易时间"].dt.year df["月"] = df["交易时间"].dt.month df["日"] = df["交易时间"].dt.day df["星期"] = df["交易时间"].dt.dayofweek # 0=周一 # 数值分箱:把年龄分成青年/中年/老年 df["年龄段"] = pd.cut(df["年龄"], bins=[0, 30, 50, 100], labels=["青年", "中年", "老年"]) # 类别 one-hot df = pd.get_dummies(df, columns=["城市"], prefix="城市", drop_first=True)pd.to_datetime的errors="coerce"把无法解析的日期变成 NaT,避免整列报错。pd.cut的bins和labels要按业务定,别硬套。get_dummies的drop_first=True去掉一个哑变量避免共线性,做线性回归时尤其要注意。这些特征构造完后,你的df就从原始表变成了可以喂给scikit-learn的矩阵。注意:one-hot 后列数会膨胀,如果类别太多(比如超过 50 个),考虑用目标编码或频率编码替代。
4. 挖掘建模:从聚类到分类的最小可跑通路径
4.1 无监督先跑 KMeans:确定簇数的肘部法和轮廓系数
拿到特征矩阵后,如果数据没有标签,先做聚类探索。KMeans 是最容易上手的,但难点在确定n_clusters。我一般同时看肘部法(inertia)和轮廓系数:
from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score # 选数值特征列,标准化 feature_cols = ["年龄", "销售额", "交易时间"] # 按你实际列名改 X = df[feature_cols].dropna() scaler = StandardScaler() X_scaled = scaler.fit_transform(X) inertias, silhouettes = [], [] k_range = range(2, 11) for k in k_range: km = KMeans(n_clusters=k, random_state=42, n_init=10) labels = km.fit_predict(X_scaled) inertias.append(km.inertia_) silhouettes.append(silhouette_score(X_scaled, labels)) for k, inertia, sil in zip(k_range, inertias, silhouettes): print(f"k={k}, inertia={inertia:.2f}, silhouette={sil:.3f}")逻辑说明:StandardScaler把不同量纲的特征拉到同一尺度,否则销售额大的列会主导距离计算。n_init=10让 KMeans 用不同初始化跑 10 次取最优,避免局部最优。inertia越小簇越紧,但会随 k 增大单调下降,所以看拐点;silhouette越接近 1 越好,一般选轮廓系数最高的 k,同时结合肘部拐点。如果两个指标冲突,优先选轮廓系数高的,因为肘部法有时拐点不明显。
4.2 有监督分类:用 train_test_split 和随机森林跑通基线
如果数据有标签列(比如是否流失),那就做分类。别一上来就调参,先跑一个随机森林基线,看特征重要性:
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix # 假设标签列叫 "标签" X = df.drop(columns=["标签"]) y = df["标签"] # 类别特征先 one-hot,数值特征保持 X = pd.get_dummies(X, drop_first=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) rf = RandomForestClassifier(n_estimators=100, random_state=42, class_weight="balanced") rf.fit(X_train, y_train) y_pred = rf.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred)) # 特征重要性 importances = pd.Series(rf.feature_importances_, index=X.columns).sort_values(ascending=False) print(importances.head(10))stratify=y保证训练集和测试集里标签比例一致,类别不平衡时必加。class_weight="balanced"自动给少数类更高权重,比手动过采样省事。classification_report看 precision、recall、f1,别只看 accuracy,不平衡数据下 accuracy 会骗人。特征重要性排前几的列,就是你后续做特征筛选或业务解释的重点。如果效果太差,再考虑调n_estimators、max_depth或换 GradientBoosting。
4.3 模型评估与交叉验证:别用一次 train_test_split 定生死
单次划分的评估结果波动很大,尤其是小数据集。我习惯用 5 折交叉验证看稳定性:
from sklearn.model_selection import cross_val_score scores = cross_val_score(rf, X, y, cv=5, scoring="f1_weighted") print(f"5折F1:{scores}") print(f"均值:{scores.mean():.3f},标准差:{scores.std():.3f}")cv=5把数据分 5 份轮流做验证,scoring选f1_weighted兼顾类别不平衡。标准差大于 0.05 说明模型不稳定,可能是数据量太小或特征噪声大。这时候别急着上深度学习,先回去检查特征工程和缺失处理。交叉验证的耗时是单次划分的 5 倍,但能帮你避免“调参调到过拟合”的翻车现场。
5. 避坑与排查:实训数据挖掘里最容易翻车的 5 个点
5.1 编码错误反复出现,换了 gbk 还是乱码
现象:pd.read_csv报UnicodeDecodeError,换成 gbk 后部分行仍然乱码。原因:文件可能是 utf-8-sig(带 BOM)或混合编码。解决:先试encoding="utf-8-sig",再试gb18030(比 gbk 覆盖更全),最后用chardet检测:
import chardet with open("shixun_data/data.csv", "rb") as f: print(chardet.detect(f.read(10000)))拿到置信度最高的编码再读。如果还不行,用errors="replace"强行读入,再手动清洗乱码字符。
5.2 列名带空格或特殊字符,后续引用报 KeyError
现象:df["销售额"]报 KeyError,但打印列名明明有。原因:列名前后有空格或不可见字符。解决:读入后立刻统一清洗列名:
df.columns = df.columns.str.strip().str.replace(" ", "_").str.replace(r"[^\w]", "", regex=True)这行把空格换下划线,去掉非字母数字下划线字符。清洗后再引用就不会出玄学问题。
5.3 数值列被读成 object,describe 只给计数
现象:df.info()显示某列 dtype 是 object,但肉眼看去全是数字。原因:列里混了“未知”“-”或千分位逗号。解决:先替换脏字符再转类型:
df["销售额"] = df["销售额"].replace({"未知": np.nan, "-": np.nan, ",": ""}, regex=True).astype(float)regex=True让逗号替换生效。转完后用df["销售额"].isnull().sum()确认缺失数量,再决定填充策略。
5.4 KMeans 跑出来所有样本一个簇
现象:聚类结果只有一个标签,轮廓系数报错。原因:特征没标准化,或者某个特征方差极大主导了距离。解决:确认StandardScaler已应用,并检查是否有常量列(方差为 0)导致距离计算失效。用df[feature_cols].nunique()看每列唯一值数量,等于 1 的列直接删掉。
5.5 交叉验证分数远低于单次划分
现象:train_test_split的 f1 有 0.9,交叉验证只有 0.6。原因:单次划分恰好把容易的样本分到了测试集,或者数据有顺序(比如按时间排列)导致折间分布差异大。解决:分类任务用StratifiedKFold,时间序列用TimeSeriesSplit,并且打乱数据前先确认没有泄漏(比如用未来信息预测过去)。如果折间方差大,考虑增加数据量或减少特征。
6. 把实训数据变成可复用的分析模板
走到这里,你已经能把一个实训数据.zip从解压读到建模评估。但下次换个压缩包,难道还要重写一遍?我的习惯是抽一个最小模板函数,把读取、清洗、类型转换、缺失处理串起来,参数化文件路径和编码:
def load_and_clean(path, encoding="utf-8", target_col=None): df = pd.read_csv(path, encoding=encoding) df.columns = df.columns.str.strip().str.replace(" ", "_") df = df.drop_duplicates() # 数值列脏字符清洗 for col in df.select_dtypes(include="object").columns: df[col] = df[col].replace({"未知": np.nan, "-": np.nan}) # 缺失填充 for col in df.columns: if df[col].dtype == "object": df[col] = df[col].fillna(df[col].mode()[0] if not df[col].mode().empty else "未知") else: df[col] = df[col].fillna(df[col].median()) return df这个函数不完美,但能覆盖 80% 的实训数据场景。参数target_col暂时没用上,你可以扩展成自动分离特征和标签。验证方法很简单:拿同一批数据跑两次,确认输出形状和缺失数一致。如果结果不稳定,检查mode()是否返回多个值导致填充不一致。
另一个实用技巧是给每个数据集存一份profile报告,用df.describe(include="all").to_csv("profile.csv")落盘,下次直接看文件,不用重新跑代码。我一般还会把清洗后的数据存成 parquet 格式,读取速度比 CSV 快好几倍:
df.to_parquet("shixun_data/cleaned.parquet", index=False) # 下次直接 pd.read_parquet("shixun_data/cleaned.parquet")parquet 保留 dtype,不会出现读回来又变 object 的后悔药场景。唯一注意的是 parquet 对中文列名支持没问题,但某些旧版 pandas 需要装pyarrow,pip install pyarrow即可。
最后说个我自己的教训:早期做实训时,我总想一步到位把模型调到最优,结果在特征工程上偷懒,用原始列硬跑,最后 f1 卡在 0.5 上不去。后来强迫自己先花 70% 时间做数据理解和清洗,模型只用默认参数,反而轻松到 0.8。数据挖掘这行,脏数据才是最大的黑匣子,你把它拆明白了,后面都是顺水推舟。希望帮到你。
本文还有配套的精品资源,点击获取