探索性数据分析EDA实战:从数据体检到异常值处理全流程

发布时间:2026/10/13 20:14:48

探索性数据分析EDA实战:从数据体检到异常值处理全流程 数据分析这个活儿干过的人都知道最耗时间的往往不是跑模型而是前期对着一个陌生数据集反复摸索这列是什么含义、缺失值多不多、分布有没有长尾、特征之间是不是在打架。EDAExploratory Data Analysis探索性数据分析就是把这些问号一个个拉直的过程。它不挑行业电商看订单、金融看风控、运营看留存、制造业看良率套路都是通的。这篇内容我打算把EDA从思路到代码完整拆一遍重点讲清楚每一步为什么这么做、参数怎么定、坑在哪里代码以Python的pandas、matplotlib、seaborn为主全部可直接复现。适合刚入行的数据分析新人也适合做了几年但一直靠感觉做EDA、想系统化补课的老手。1. EDA到底在解决什么问题1.1 先搞清楚EDA的定位别把它当成画图任务很多人对EDA的理解停留在“画几个图看看分布”这是把手段当成了目的。EDA的本质是一次低成本的信息侦察在正式建模或出结论之前用最小的代价摸清数据的底细判断哪些假设成立、哪些特征值得留、哪些数据质量问题会直接毁掉后续分析。它和建模的关系有点像装修前先量房——你不量后面买多少材料都是瞎猜。我习惯把EDA要回答的问题归成四类数据长什么样结构、类型、规模、数据干不干净缺失、重复、异常、格式错乱、数据里藏着什么规律分布、相关性、分组差异、这些规律可不可信样本偏差、幸存者偏差、时间泄漏。这四个问题回答完一份EDA才算及格。只画了直方图就收工等于量房只量了客厅。从投入产出比看EDA是整个分析流程里性价比最高的环节。一个中型数据集认真做一轮EDA大概两三个小时但能帮你省掉后面反复返工的好几天。我见过太多案例模型效果死活上不去回头一查是某列日期格式解析错了或者某个特征里混进了测试集的数据。这些问题在EDA阶段花五分钟就能发现拖到建模阶段就是灾难。1.2 为什么EDA必须用代码而不是纯手工有人会问用Excel点点筛选、拖拖透视表不也能看数据吗小数据可以但只要数据量过万行、列数过几十手工操作就会崩。原因有三个一是不可复现你今天点出来的结论明天换个人、换个顺序就复现不了二是不可扩展换一份新数据你得从头再点一遍三是容易漏人眼扫几千行必然疲劳异常值、格式错误很容易被忽略。用代码做EDA核心价值在于把探索过程沉淀成可复用的脚本。第一遍写完第二份数据改个路径就能跑甚至能封装成函数自动出报告。这也是为什么我坚持用pandas而不是纯Excel——它的链式操作、分组聚合、缺失值处理能力配合matplotlib和seaborn的可视化基本覆盖了EDA九成以上的需求。下面所有代码都基于这套组合环境用Python 3.9以上即可。提示EDA脚本不要追求一次写完美先跑通再优化。我通常先写一个粗糙的探索脚本边看结果边加代码最后再整理成规范版本。2. 数据加载与初步体检2.1 加载数据时的编码与类型陷阱拿到数据第一步是读进来这一步看着简单坑却不少。最常见的是编码问题中文数据经常遇到GBK和UTF-8打架读进来一堆乱码。我的习惯是先试UTF-8报错就换GBK再不行用chardet库探测。另一个坑是类型推断错误比如订单号是纯数字pandas默认读成int但订单号根本不该参与数值计算应该强制指定为字符串。import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 设置中文显示避免图表乱码 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 加载数据显式指定关键列类型 df pd.read_csv( data.csv, encodingutf-8, dtype{order_id: str, user_id: str}, parse_dates[order_date] ) print(df.shape) print(df.dtypes) print(df.head())这里dtype参数是关键把ID类字段强制成字符串避免后面做聚合时被当成数值求和。parse_dates让日期列直接解析成datetime类型省得后面手动转换。这两个参数看起来不起眼但能省掉大量返工。2.2 用info和describe做第一轮体检数据读进来后先别急着画图用info()和describe()做一次快速体检。info()告诉你每列的非空数量和类型describe()给出数值列的统计量。这两个输出加起来基本能判断出数据规模和大致质量。df.info() df.describe(includeall).T看info()的时候重点盯两件事非空数量是否和总行数一致不一致说明有缺失类型是否符合预期比如本该是数值的列显示成object多半是混进了非数字字符。看describe()的时候重点看min和max如果年龄出现200、金额出现负数异常值基本就藏在这里。我一般会额外算一个缺失率表比info()更直观missing df.isnull().sum() missing_rate (missing / len(df) * 100).round(2) missing_df pd.DataFrame({缺失数: missing, 缺失率%: missing_rate}) print(missing_df[missing_df[缺失数] 0].sort_values(缺失率%, ascendingFalse))这张表能直接告诉你哪些列缺失严重、需要特殊处理。缺失率超过50%的列除非业务上极其重要否则我一般直接考虑丢弃因为填充带来的偏差可能比丢掉更大。2.3 重复值与唯一值的快速排查重复值是最容易被忽略的数据质量问题。完全重复的行好处理drop_duplicates()一把梭。麻烦的是业务意义上的重复比如同一个用户同一天下了两笔一模一样的订单可能是系统重复写入也可能是真实的两笔。这种就得结合业务判断不能无脑删。# 完全重复行 print(f完全重复行数: {df.duplicated().sum()}) # 按业务主键查重复 dup df[df.duplicated(subset[user_id, order_date], keepFalse)] print(f疑似业务重复: {len(dup)})唯一值排查用nunique()能快速识别哪些列是常量、哪些列基数过高。基数过高的类别列比如用户ID不适合直接做分组分析基数过低的比如只有一两个值基本没有分析价值。unique_df pd.DataFrame({ 唯一值数: df.nunique(), 类型: df.dtypes }) print(unique_df.sort_values(唯一值数))3. 缺失值与异常值处理3.1 缺失值的三种处理策略与选择依据缺失值处理没有万能公式核心是判断缺失是随机的还是有信息的。随机缺失比如问卷里有人漏填了年龄可以填充有信息的缺失比如高收入人群故意不填收入填充反而会引入偏差。判断方法很简单把“是否缺失”当成一个特征看它和其他列有没有相关性。# 构造缺失指示变量检查缺失是否有信息 df[income_missing] df[income].isnull().astype(int) print(df.groupby(income_missing)[age].mean())如果缺失组和非缺失组的年龄均值差异明显说明缺失不是随机的这时候填充要谨慎。处理策略上我一般分三档缺失率低于5%的数值列用中位数填充、类别列用众数填充5%到30%的考虑用模型预测填充或保留缺失作为一个独立类别超过30%的除非业务强相关否则直接丢弃。中位数比均值抗异常值这是我优先选中位数的原因。# 数值列中位数填充 num_cols df.select_dtypes(include[np.number]).columns for col in num_cols: if df[col].isnull().sum() 0: df[col].fillna(df[col].median(), inplaceTrue) # 类别列众数填充 cat_cols df.select_dtypes(include[object]).columns for col in cat_cols: if df[col].isnull().sum() 0: df[col].fillna(df[col].mode()[0], inplaceTrue)3.2 异常值识别IQR与Z-score怎么选异常值识别主流有两种方法IQR四分位距和Z-score。IQR基于分位数对分布没有假设适合偏态数据Z-score基于均值和标准差假设数据近似正态适合对称分布。选哪个取决于你的数据分布不能一刀切。IQR的判定逻辑是低于Q1-1.5×IQR或高于Q31.5×IQR的值算异常。这个1.5是经验系数调大调小会改变敏感度。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)] outliers detect_outliers_iqr(df[amount]) print(f异常值数量: {len(outliers)}, 占比: {len(outliers)/len(df)*100:.2f}%)Z-score的判定是绝对值大于3算异常这个3对应正态分布下约99.7%的覆盖范围。from scipy import stats z_scores np.abs(stats.zscore(df[amount].dropna())) outlier_mask z_scores 3 print(fZ-score异常值数量: {outlier_mask.sum()})注意识别出异常值不等于要删除。异常值可能是真实的高价值客户也可能是录入错误。我的做法是先标记、先看结合业务判断绝不无脑删。删之前一定先看看这些异常值长什么样。3.3 异常值处理的实操心得处理异常值我踩过最大的坑是早期做用户消费分析时直接把金额最高的1%删了结果把几个大客户删没了结论完全跑偏。后来我总结出一个原则先分类再处理。把异常值分成三类——录入错误比如年龄200、业务真实极端值比如大客户、未知原因分别对待。录入错误直接修正或置空业务真实极端值保留必要时做对数变换压缩量级未知原因的先保留并标记在报告里说明。对数变换是处理右偏分布的利器能把长尾压平让分布更接近正态对后续建模也友好。# 对数变换处理右偏 df[amount_log] np.log1p(df[amount]) fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.histplot(df[amount], axaxes[0], kdeTrue) axes[0].set_title(原始分布) sns.histplot(df[amount_log], axaxes[1], kdeTrue) axes[1].set_title(对数变换后) plt.tight_layout() plt.show()np.log1p是log(1x)专门处理含0的数据避免log(0)报错。这个细节很多人不注意直接用np.log遇到0就出-inf后面全乱套。4. 单变量与多变量分析4.1 单变量分布分析直方图、箱线图、小提琴图单变量分析是EDA的地基目的是搞清楚每一列自己的脾气。数值列看分布形状、集中趋势、离散程度类别列看各类占比。工具上直方图看整体分布箱线图看异常值和四分位小提琴图结合了两者还能看密度各有各的用处。fig, axes plt.subplots(1, 3, figsize(15, 4)) sns.histplot(df[age], bins30, kdeTrue, axaxes[0]) axes[0].set_title(年龄分布直方图) sns.boxplot(ydf[age], axaxes[1]) axes[1].set_title(年龄箱线图) sns.violinplot(ydf[age], axaxes[2]) axes[2].set_title(年龄小提琴图) plt.tight_layout() plt.show()看直方图重点判断偏态和峰态右偏说明有长尾高值左偏说明有长尾低值双峰说明数据可能来自两个不同群体。箱线图重点看中位数位置和须的长度中位数偏离箱子中心说明偏态。小提琴图看密度形状能发现直方图看不出的多峰结构。类别列用value_counts()配合条形图重点看类别是否均衡。严重不均衡的类别列做分组分析时小类别的结论不可靠样本太少。cat_col channel vc df[cat_col].value_counts() print(vc) print(f占比:\n{(vc / len(df) * 100).round(2)}) sns.barplot(xvc.index, yvc.values) plt.title(f{cat_col} 类别分布) plt.xticks(rotation45) plt.show()4.2 相关性分析皮尔逊、斯皮尔曼与热力图多变量分析里相关性是重头戏。皮尔逊相关系数衡量线性关系斯皮尔曼衡量单调关系不要求线性两者适用场景不同。数据近似正态、关系线性用皮尔逊有异常值或关系非线性用斯皮尔曼。我一般两个都算对比着看。num_df df.select_dtypes(include[np.number]) pearson_corr num_df.corr(methodpearson) spearman_corr num_df.corr(methodspearman) plt.figure(figsize(12, 10)) sns.heatmap(pearson_corr, annotTrue, fmt.2f, cmapcoolwarm, center0, squareTrue, linewidths0.5) plt.title(皮尔逊相关系数热力图) plt.show()看热力图重点找绝对值大于0.7的强相关对。强相关的特征在建模时容易造成多重共线性通常保留一个即可。但要注意相关不等于因果两个特征高度相关可能只是都被第三个因素驱动。另外皮尔逊系数对异常值极其敏感一个极端值就能把系数从0.1拉到0.8所以算之前最好先处理异常值。提示热力图上如果发现某两个特征相关系数接近1先别急着删去看看它们的业务含义。有时候是重复采集有时候是派生关系比如“总价”和“单价×数量”处理方式不一样。4.3 分组对比分析透视表与分组聚合分组分析是EDA里最能出业务洞察的部分。核心思路是按某个维度切分看目标指标在不同组的表现差异。pandas的groupby配合agg能一次性算出多个统计量效率很高。group_stats df.groupby(channel).agg( 用户数(user_id, nunique), 平均金额(amount, mean), 中位金额(amount, median), 订单数(order_id, count), 金额标准差(amount, std) ).round(2) print(group_stats.sort_values(平均金额, ascendingFalse))这里我特意同时算了均值和中位数因为均值容易被极端值带偏。如果某组均值和
延伸阅读

更多相关文章

2026/10/13 20:14:48

从Transformer原理到模型落地:微调、量化与部署实战指南

讲个真实的问题:Transformer的原理我读得懂,多头注意力、位置编码、残差连接这些概念都能说出来,代码也跟着教程敲过几遍,可一旦要把模型真正用起来——微调、部署、接进业务里——就立刻卡壳。这个问题不是我一个人遇到过&#x…

2026/10/13 20:09:47

jsvmp逆向实战:某音a_b参数纯算还原全记录

1. 项目整体思路与目标拆解1.1 a_b参数是什么,为什么要啃它先说点实在的。a_b参数是某音Web端请求里一个高频出现的query参数,几乎所有核心接口都带着它,服务端拿它做风控判断。这个参数不像普通签名那样把入参拼一拼再做个md5就完事&#xf…

2026/10/13 20:09:47

2026年安全规划:AI攻防、零信任与量子安全的组合拳

2026 年还没到,但安全团队已经开始睡不着了。上周和一个在甲方做安全负责人的朋友聊到凌晨,他一句话让我印象很深:现在的攻击已经不是“黑客发现了漏洞然后利用”,而是“AI 批量扫漏洞、批量生成钓鱼信、批量试探口令,…

2026/10/13 21:30:01

C#面向对象实战:自动出题判分小程序设计与实现

1. 项目缘起与整体设计思路1.1 为什么选“自动出题判分”作为面向对象的练手项目《C#语言程序设计》这门课学到面向对象部分,很多人会卡在一个尴尬的节点:语法都懂,类、继承、多态、接口这些词也能背出来,但真让你从零设计一个能跑…

2026/10/13 21:30:01

遗传算法优化SVM参数实战:GA调参RBF核C与gamma的工程指南

简介:这份资源面向具备一定Python与机器学习基础、希望掌握模型调参实战的学习者,聚焦用遗传算法(GA)自动优化SVM分类模型的超参数,解决人工调参效率低、易陷入局部最优的问题。压缩包共5个文件,包含3个py脚…

2026/10/13 21:30:01

PenguinHarness:用一行命令创建生产级AI智能体

1. 项目概述:当“造 Agent”变成一句命令,我们到底省下了什么?最近在几个技术社区刷到一句特别扎眼的话:“这波有点狠:Agent 还在手搓?PenguinHarness 0.2.1 把‘造 Agent’压成一句话。”——不是宣传稿&a…

2026/10/13 21:30:01

白银票据攻击原理与防御:Kerberos服务票据伪造实战解析

1. 白银票据不是“银票”,而是域控环境里的一把隐形钥匙很多人第一次听到“白银票据”这个词,下意识会联想到古装剧里盖着红印、写着“凭票兑银五百两”的那种纸质银票。但现实中的白银票据(Silver Ticket)和它毫无关系——它既不…

2026/10/13 21:30:00

Qt+MySQL教务系统毕设实战:从建库到选课事务与打包避坑

简介:这份资源是面向计算机相关专业在校学生与教师的Qt教务系统完整项目源码,采用C与Qt框架开发,后端使用MySQL数据库,支持学生、教师、管理员三种身份登录与权限区分,可作为毕业设计、课程设计或大作业的参考实现。压…

2026/10/13 21:24:59

用Python标准库从零构建命令行待办事项工具

命令行工具这种东西,一旦用习惯了就很难再回到图形界面。图形界面适合复杂交互,但真正高频、小粒度的操作,往往还是终端里敲一条命令来得痛快。待办事项管理就是特别典型的场景——打开一个App、找到列表、点新建、输入内容,这中间…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/13 0:04:41

基于Triton部署YOLO目标检测:从训练脚本到线上服务

简介:这份资源面向希望将目标检测模型落地到生产环境的深度学习开发者与算法工程师,聚焦于用 Triton Inference Server 部署 YOLOv9 的完整实战方案,解决从训练权重到线上推理服务之间的工程化难题。压缩包共 16 个文件,约 886KB&…

2026/10/13 0:04:41

手把手教你云端部署Qwen-Image-2.1:从GPU选型到在线文生图服务

Qwen-Image-2.1 这个开源的图像生成模型,最近在内容创作和 AI 应用圈子里热度相当高。它属于通义 Qwen 系列里的视觉生成分支,让我眼前一亮的是它对中文长文本的理解能力——以前用那些老方案生成带中文文字的图,十个里九个是“乱码印刷体”&…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑