机器学习中的数据理解:EDA标准流程与pandas实战指南
如果只让我从“100 天机器学习计划”里挑一个最容易被跳过、却又最能决定项目成败的环节我会选“理解数据”。第 19 天的主题就是这件事先别急着调模型先把数据看清楚。数据集的分布、缺失、异常、重复、类型错乱这些问题在建模阶段会被放大在部署阶段会变成事故。这篇文章按 CampusX 100 天计划的思路把“理解你的数据”拆成一套可以直接照做的流程环境搭建、EDA 标准步骤、pandas 实战代码、可视化检查、以及数据理解结果如何影响后续建模决策。本文会覆盖数据理解在机器学习流程中的位置、需要准备哪些工具、六步数据探查流程、可复制的 Python 代码、常见坑点和排查清单。适合刚开始学机器学习、准备做第一个完整项目、或者已经在跑模型但效果始终不理想的人。核心结论先说大多数模型效果差不是算法不行而是数据理解不到位。1. 核心能力速览能力项说明所属阶段机器学习项目中的数据处理与探索性数据分析EDA阶段主要目标识别数据质量问题和分布规律为特征工程与模型选择提供依据核心工具Python、Pandas、NumPy、Matplotlib、Seaborn、Missingno运行环境Jupyter Notebook / VS Code / Kaggle Notebook 均可硬件要求普通办公电脑即可不需要 GPU适用数据集表格数据、CSV、数据库导出、爬虫结果、公开数据集输出产物数据概览报告、缺失值报告、类型修正记录、可视化图表、特征决策清单是否支持批量支持可用脚本对多个数据文件执行统一检查是否支持 API不需要本地脚本即可完成也可封装成函数复用前置知识Python 基础、pandas 基础即可这里先明确一个概念数据理解不等于数据可视化也不等于数据清洗。它指的是在建模之前通过统计描述和可视化手段搞清楚数据集的规模、结构、质量、分布和变量间关系。这个环节在学术上常叫探索性数据分析EDA在工程上就是“动手写代码前先和你的数据聊聊天”。2. 适用场景与使用边界数据理解是几乎所有机器学习任务的共同前置步骤。适合的场景包括拿到一份新数据集不知道从哪下手时。模型训练完效果差怀疑是数据问题但找不到证据时。准备做特征工程需要判断哪些列值得保留、哪些列需要转换时。数据集来自多个来源合并需要检查 schema 是否一致时。学习阶段练习公开数据集例如泰坦尼克号、房价预测、鸢尾花、MNIST 等都需要先理解数据再做建模。数据理解不能解决所有问题。它无法弥补数据采集阶段的系统性偏差。比如某个分类任务的正负样本比例失衡EDA 能帮你发现这个问题但如果你没有额外数据EDA 本身不能帮你生成新样本。它也不能替代数据标注质量控制——标注错误的样本在分布图上可能并不明显需要结合业务规则去筛查。还有一个边界需要注意当数据集来自公开渠道或爬虫时务必确认数据的使用许可。涉及用户隐私、人脸、语音等敏感数据必须遵守相关法律法规获得合法授权后才能处理。不要下载来源不明、可能包含未授权个人信息的数据集用于商用项目。3. 环境准备与前置条件理解数据不需要 GPU不需要 CUDA不需要大显存。普通笔记本就能跑。建议准备以下环境Python 3.9 或更高版本。Jupyter Notebook、JupyterLab 或 VS Code。Anaconda 或 venv 虚拟环境。需要安装的核心库pip install pandas numpy matplotlib seaborn missingno openpyxl各库的作用库用途pandas读取、切片、统计、清洗数据numpy数值计算处理数组和缺失值matplotlib基础绘图seaborn统计可视化画分布图和相关性热力图missingno快速可视化缺失值分布openpyxl读取 Excel 文件建议在项目目录下按这样的结构整理文件避免后面建模时找不到数据project/ ├── data/ │ ├── raw/ # 原始数据只读不改 │ ├── processed/ # 清洗后的数据 │ └── external/ # 外部参考数据 ├── notebooks/ # Jupyter 分析脚本 ├── scripts/ # 可复用的 Python 脚本 └── output/ ├── figures/ # EDA 图表 └── reports/ # 数据总结报告第一次做数据理解建议直接使用 Jupyter Notebook。它的交互式特点非常适合逐步探索读数据、看结果、再决定下一步。如果对 Jupyter 的安装和内核配置不熟悉可以先跑一遍 Kaggle 或 Colab 在线环境确认代码逻辑没问题再切回本地。4. 数据理解的标准流程建议按下面六步走。每一步解决一个问题不要跳步。4.1 数据概览先看规模再看结构拿到数据集后第一件事不是画图而是回答几个基础问题数据有多少行、多少列每一列是什么数据类型列名是否清晰有没有重名或空列名数据集是从哪里来的采集时间范围是什么对应的 pandas 操作import pandas as pd # 读取数据 df pd.read_csv(data/raw/train.csv) # 行数和列数 print(df.shape) # 列名、数据类型、非空数量 print(df.info()) # 前 5 行 print(df.head())通过这几行代码你能立刻知道数据集是 100 行还是 100 万行有没有明显的内存压力以及是否存在大量空值列。如果数据集行数很大比如超过 50 万行后面做可视化时要注意抽样避免绘图卡死。4.2 统计摘要与数据类型检查确认结构后对数值列做统计摘要print(df.describe())describe()会输出每列的数量、均值、标准差、最小值、四分位数和最大值。从这些数字能快速看出几类问题值域异常。某列最大值比 99% 分位数高出几个数量级大概率有异常值或离群点。均值远大于中位数说明数据右偏后面可能要做对数变换。某列数量明显少于总行数说明存在缺失。这组统计结果是后面所有决策的基准。建议把关键结论记录下来例如“age 列有 177 个缺失值”“fare 列存在极端值最大值 512第 75 分位是 31”。4.3 缺失值与重复值检查缺失值处理是数据理解最重要的一环。先用代码确认缺失的全貌# 缺失值数量 missing df.isnull().sum() missing missing[missing 0].sort_values(ascendingFalse) print(missing)用 missingno 库可以快速可视化缺失分布判断缺失是否是随机散布的import missingno as msno import matplotlib.pyplot as plt msno.matrix(df) plt.show()如果矩阵图的黑色条带集中在某些行说明缺失存在规律可能和采集批次、渠道有关。这种非随机缺失会影响模型泛化需要在后续特征工程中单独处理而不是简单删除。重复值检查也容易漏print(df.duplicated().sum()) print(df.duplicated(subset[user_id]).sum())注意整体重复和指定关键列重复含义不同。如果按业务主键如用户 ID查重发现重复行但其他字段不同说明数据来源存在一主多从关系需要决定保留策略。4.4 分布分析与可视化对数值列画直方图和箱线图对类别列画柱状图。这一步的目标是看清每一列的分布形态。import seaborn as sns import matplotlib.pyplot as plt # 数值列分布 df.hist(figsize(12, 10), bins50) plt.tight_layout() plt.show() # 类别列频次 categorical_cols df.select_dtypes(include[object, category]).columns for col in categorical_cols[:3]: df[col].value_counts().plot(kindbar, titlecol) plt.show()从分布图需要获得以下信息列是否呈正态分布、偏态分布、多峰分布。类别列是否有一个类别占比过高可能导致类别不平衡。类别种类太多的列如城市名有几千种后面需要做频次编码或在特征工程中合并低频类别。4.5 相关性分析与变量关系相关性分析用于判断特征和目标变量之间的线性关系以及特征之间的共线性。数值列之间用相关系数矩阵和热力图corr df.select_dtypes(include[float64, int64]).corr() sns.heatmap(corr, annotTrue, cmapcoolwarm, fmt.2f) plt.show()特征之间相关系数绝对值超过 0.8说明存在较强共线性在回归模型中可能导致系数不稳定。实践中可以用皮尔逊相关快速筛选但对非线性关系要用散点图或分组统计进一步确认。类别列与数值目标的关系可以用分组聚合。例如想确认“学历”和“收入”的关系df.groupby(education)[income].mean().sort_values(ascendingFalse)4.6 异常值检测异常值不等于错误值。判断异常值是否要处理取决于它是否来自真实业务。常用检测方法箱线图法超过 Q1 - 1.5 * IQR 或 Q3 1.5 * IQR。分位数法超过 99% 分位数或低于 1% 分位数。业务规则法例如年龄为负数、价格小于等于 0 但业务上不可能。import numpy as np def detect_outliers_iqr(series): q1 series.quantile(0.25) q3 series.quantile(0.75) iqr q3 - q1 lower q1 - 1.5 * iqr upper q3 1.5 * iqr return series[(series lower) | (series upper)].index这里要特别提醒不要一发现异常值就删除。先看数据的业务含义。比如“交易金额”列出现 100 万可能不是错误而是大额交易。异常值是否删除要和业务方确认后再决定。5. 实战一套可复用的数据理解脚本把上面的步骤整合成一个函数之后遇到任何新数据集都可以直接复用。这段代码适合放在scripts/eda_summary.py里。import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import missingno as msno def eda_summary(df, targetNone, output_diroutput): 对 DataFrame 执行基础 EDA输出统计报告和关键图表。 import os os.makedirs(output_dir, exist_okTrue) print( * 50) print(数据集规模:, df.shape) print( * 50) # 1. 数据类型和缺失概览 summary pd.DataFrame( { dtype: df.dtypes.astype(str), non_null_count: df.notnull().sum(), missing_count: df.isnull().sum(), missing_pct: (df.isnull().mean() * 100).round(2), unique_count: df.nunique(), } ) print(\n【缺失值概览】) print(summary[summary[missing_count] 0]) # 2. 数值列统计摘要 num_cols df.select_dtypes(include[np.number]).columns.tolist() if num_cols: print(\n【数值列统计摘要】) print(df[num_cols].describe().T) # 3. 缺失值矩阵图 if len(summary[summary[missing_count] 0]) 0: msno.matrix(df) plt.savefig(f{output_dir}/missing_matrix.png, dpi100) plt.show() # 4. 相关性热力图 if len(num_cols) 1: corr df[num_cols].corr() plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, fmt.2f, cmapcoolwarm) plt.title(Correlation Heatmap) plt.tight_layout() plt.savefig(f{output_dir}/correlation.png, dpi100) plt.show() # 5. 目标变量分布如果指定 if target is not None and target in df.columns: plt.figure(figsize(8, 4)) sns.histplot(df[target], kdeTrue) plt.title(fTarget Distribution: {target}) plt.tight_layout() plt.savefig(f{output_dir}/target_dist.png, dpi100) plt.show() return summary调用方式from eda_summary import eda_summary df pd.read_csv(data/raw/train.csv) summary eda_summary(df, targetprice)这个脚本输出三样东西控制台的统计摘要、缺失值矩阵图、相关性热力图。批量检查多个文件时只需要在外层加一个循环import glob for path in glob.glob(data/raw/*.csv): df pd.read_csv(path) name path.split(/)[-1].replace(.csv, ) eda_summary(df, output_dirfoutput/{name})到这里数据理解的核心工作已经完成。下一步才是清洗和建模。6. 数据理解结果如何影响建模决策数据理解不是终点它的价值体现在后续每个环节的决策上。特征选择。如果两个特征相关性极高可以考虑只保留一个或做 PCA 降维。如果某个特征的缺失比例超过 50%且和业务关联度低建议直接删除。模型选择。数据量小、变量多优先选择线性模型、正则化模型或树模型。数据量大、变量间关系复杂可以考虑集成模型。如果类别严重不平衡需要调整采样策略或使用适合不平衡数据的评估指标而不是只用准确率。是否需要数据增强。如果某个类别样本极少而业务上这个类别很重要可以考虑数据增强方法例如对表格数据做 SMOTE 过采样对图像数据做翻转、旋转、裁剪等变换。但增强方法要谨慎使用盲目生成样本可能引入噪声。是否需要特征变换。右偏分布的特征尤其是存在极端值的可以用对数变换或 Box-Cox 变换。类别变量种类很多可以按频次编码。时间列可以拆成年、月、日、星期几等。是否需要重新收集数据。如果缺失比例过高、类别严重失衡且无法通过采样解决、关键字段大量重复这时最理性的决策不是写更多的清洗代码而是找数据源补充数据或重新明确数据定义。这些决策全部依赖第 4 节的探查结果。所以数据理解做得越完整后续试错成本越低。7. 常见问题与排查方法问题现象可能原因排查方式解决方案df.read_csv()报 UnicodeDecodeError文件编码不是 UTF-8用文本编辑器看文件编码指定encodinggbk或encodingutf-8重试读取后列名乱码CSV 文件带 BOM 头或编码不一致打印df.columns观察用encodingutf-8-sig读取数字列变成 object 类型列中存在逗号、百分号或空字符串检查该列的unique()去掉符号后pd.to_numeric(..., errorscoerce)大量缺失值但在 head() 里看不出来缺失值被填充为、NA、null等df.isin([, NA, null]).sum()统一替换为np.nan后再统计画热力图报内存错误数据量太大检查行列数抽样后绘图或只计算相关特征子集聚类或统计结果不稳定重复行过多df.duplicated().sum()去重后重跑日期列看起来正常但排序不对日期是字符串类型df[date].dtype用pd.to_datetime()转换同一份数据两次读取行数不一致文件在采集或同步过程中被改动对比文件大小和修改时间固定原始数据目录不要原地修改一个很常见的坑是手工在 Excel 里打开 CSV 后保存导致编码、格式被改变。数据理解阶段建议尽量用代码读取不要手动编辑原始数据文件。原始文件保持只读所有清洗结果写入新的文件这样后续可以追溯每一步变化。8. 最佳实践与使用建议先小范围验证。拿到新数据集时先用df.sample(10000)抽一小部分跑通所有探查代码确认无误后再跑全量。大文件全量跑 EDA 可能很慢也容易让 Jupyter 内存暴涨。保留可复现环境。把环境依赖写入requirements.txt方便下一次在别的机器上复现相同的分析环境。数据目录按 raw、processed、external 分离。原始数据永远不改动清洗后的数据单独存放。生成的和下载的数据都要记录来源和时间。建议写一个简单的 README包括字段说明、单位、采集时间、数据含义。批量任务加日志。如果要对几十个 CSV 重复执行检查建议在脚本中输出每个文件的处理状态。可以参考 p 打印的方式在每次循环里记录当前文件名、行数、缺失数和处理耗时这样定位失败文件时不用翻完整日志。接口和自动化。数据理解本身不要求提供 API但如果你要在团队中推广这套流程建议把 EDA 总结封装成 Python 模块供其他同学在训练前统一调用。这样全团队的数据检查标准一致避免每个人理解数据的方式不同导致结果不可比。合规使用数据。数据理解阶段最容易忽略的是数据权限。开始分析之前先确认数据集是否可以合法使用是否包含个人信息是否允许用于商业模型如果数据来自公开竞赛平台注意赛事条款。如果涉及人脸、语音、医疗等敏感数据必须严格限制访问范围脱敏后再分析必要时咨询法务。9. 总结与下一步“理解你的数据”是 100 天机器学习计划第 19 天的重点也是任何真实机器学习项目的起跑线。这一阶段建议先验证df.info()能否一眼说清数据规模、每列类型和缺失情况df.describe()能否看出明显异常值相关性热力图能否帮助判断哪些特征可以合并或删除缺失值矩阵图能否指出缺失的规律。这四项能跑通数据理解的基本功就算过关了。最容易踩的坑有三个第一跳过 EDA 直接建模模型效果差时又回头从头查数据第二把“数据理解”和“数据清洗”混为一谈还没看清分布就开始填缺失值第三在原始数据文件上直接修改导致分析结果无法复现。建议下一步完成三件事把第 5 节的 EDA 脚本跑通并保存为自己常用工具找一份真实公开数据集完整做一次数据理解输出结构化的数据报告然后带着这份报告进入特征工程阶段这样后续每个特征处理决定都有依据。把数据理解变成习惯之后再去碰算法你会发现很多“模型玄学”问题其实早在数据阶段就已经注定。