做数据分析这行久了,你会发现一个挺有意思的现象:很多人拿到一份数据集,第一反应是赶紧跑模型、调参数,恨不得三行代码出个预测结果。但真正踩过坑的人都清楚,模型效果上不去,八成不是算法选错了,而是你在建模之前根本没把数据摸透。探索性数据分析(EDA)里的数据可视化,就是那个“摸透数据”的过程——它不是什么花哨的图表展示,而是一套用图形语言跟数据对话的方法论。你手上有一张销售表、一份用户行为日志、一批传感器读数,在写第一行特征工程代码之前,先用可视化把分布、异常、关系、趋势这些东西看清楚,后面能省掉的返工时间远超你的想象。这篇内容适合刚入行数据分析、正在学EDA流程的朋友,也适合做了几年但一直把可视化当“出图交差”的老手——我会把每一步为什么这么做、怎么选图、参数怎么调、哪里容易翻车,掰开揉碎讲清楚。
1. 先搞清楚EDA可视化到底在解决什么问题
1.1 可视化不是画图交差,是降低认知负荷的手段
很多人对EDA可视化的理解停留在“领导要看的图”这个层面,觉得画个柱状图、饼图就算完成任务了。这个认知偏差会导致一个直接后果:图是画了,但该发现的问题一个没发现。EDA可视化的本质,是把数字表格里人类肉眼难以直接感知的模式,转译成视觉系统擅长处理的形状、位置、颜色差异。一张几百行的表格,你盯着看半小时也未必能发现某个字段有5%的缺失集中在某个时间段,但一张按时间轴展开的缺失率折线图,三秒钟就能让这个模式跳出来。
从认知科学的角度讲,人眼对长度、位置、角度的敏感度远高于对原始数字的敏感度。这就是为什么散点图能让你一眼看出两个变量的相关性,而看相关系数矩阵的原始数字反而容易漏掉非线性关系。所以做EDA可视化的时候,心里要始终绷着一根弦:我画这张图,是为了回答哪个具体问题?如果回答不了任何问题,这张图就不该存在。
我见过不少分析报告,一上来就是十几张图,配色花哨、图种齐全,但看完之后你说不出这份数据到底有什么值得注意的地方。这就是典型的“为可视化而可视化”。正确的做法是带着假设去画图——比如你怀疑用户活跃度在周末会下降,那就画一张按星期分组的活跃度箱线图去验证;你怀疑某个数值字段存在录入错误导致的极端值,那就画直方图或者小提琴图去看尾部。每张图都应该是一次“提问-回答”的闭环。
1.2 单变量、双变量、多变量:三种视角对应三类图
EDA可视化的图种选择,本质上取决于你要看几个变量之间的关系。这个分类框架看起来基础,但实际工作中很多人会混淆使用场景。单变量分析关注的是“一个字段自己长什么样”,核心问题是分布形态、集中趋势、离散程度、异常值。对应的图种主要是直方图、核密度估计图、箱线图、小提琴图。这里有个容易踩的坑:直方图的分箱数量(bin数)对形态呈现影响极大,分箱太少会把双峰分布压成单峰,分箱太多又会把噪声当成结构。我的经验是先用Freedman-Diaconis规则算一个参考值,再手动微调,不要无脑用默认参数。
双变量分析关注的是“两个字段之间有没有关系”,核心问题是相关方向、相关强度、是否存在分组差异。数值对数值用散点图,数值对类别用分组箱线图或分组小提琴图,类别对类别用堆叠柱状图或马赛克图。散点图在数据量大的时候会出现严重的点重叠(overplotting),这时候要么降采样、要么用六边形分箱图(hexbin)、要么给点加透明度。我一般优先加透明度,因为降采样会丢信息,hexbin虽然解决了重叠但牺牲了单个点的可读性。
多变量分析关注的是“多个字段联合起来呈现什么结构”,核心问题是聚类倾向、维度间的交互效应。对应的手段包括散点图矩阵(pair plot)、相关性热力图、平行坐标图、降维后的二维投影(比如PCA之后画散点)。这里要提醒一句:散点图矩阵在字段超过七八个之后会变得极其拥挤,阅读体验直线下降,这时候要么分批次看,要么先做一轮相关性筛选再画。
| 分析视角 | 核心问题 | 首选图种 | 常见翻车点 |
|---|---|---|---|
| 单变量 | 分布形态、异常值 | 直方图、箱线图 | 分箱数不当导致形态误判 |
| 双变量 | 相关方向与强度 | 散点图、分组箱线图 | 大数据量点重叠 |
| 多变量 | 联合结构与聚类 | 散点图矩阵、热力图 | 字段过多导致可读性崩溃 |
1.3 为什么可视化必须放在特征工程之前
这个顺序问题值得单独拎出来说,因为它是整个EDA流程里最容易被颠倒的一环。很多人习惯先把特征工程做完——归一化、编码、填充缺失——然后再画图看看效果。这个顺序的问题在于,特征工程的很多操作本身就会改变数据的分布形态,你在变换后的数据上做可视化,看到的已经不是原始数据的真实面貌了。
举个具体的例子:你对一个长尾分布的金额字段做了对数变换,然后再画直方图,分布看起来接近正态了,你可能会误以为原始数据也差不多是正态的,从而漏掉了原始数据里那批极端大额交易。而恰恰是这批极端值,可能对应着刷单、系统故障或者特殊业务场景,是后续分析必须单独处理的对象。所以正确的顺序永远是:先对原始数据做一轮完整的可视化体检,记录下所有异常模式和分布特征,再带着这些认知去设计特征工程方案。
另一个理由是,可视化能帮你发现那些统计量掩盖不了的问题。均值、中位数、标准差这些汇总指标,在数据存在双峰分布或者大量重复值时会产生严重误导。一个经典的例子是:某字段的均值和标准差看起来很正常,但直方图一画出来,发现是明显的双峰——两个群体的数据被混在了一起。这种情况下如果你直接拿均值去做填充或者做阈值判断,两个群体都会被错误处理。可视化是唯一能在汇总层面之外保留分布细节的手段,这也是它不可替代的原因。
2. 数据体检阶段:用图把“脏”找出来
2.1 缺失值可视化:热力图比缺失率表格有用十倍
拿到数据第一步,先看缺失。很多人习惯用df.isnull().sum()打一张缺失率表格出来,看一眼哪个字段缺失高就完事了。这个做法的问题在于,它只告诉你“缺多少”,不告诉你“怎么缺”。而缺失的分布模式往往比缺失率本身更有信息量。比如某个字段整体缺失率只有8%,看起来不高,但如果这8%全部集中在某个时间段或者某个用户群体,那它就不是随机缺失,而是有结构性原因的,处理方式完全不同。
用缺失值热力图(missingno矩阵图或者自己用plt.imshow画)能直观看到缺失的分布模式。横轴是样本索引,纵轴是字段,有值的地方一种颜色,缺失的地方另一种颜色。如果缺失呈现明显的条带状或者块状,说明缺失跟某些条件相关;如果缺失是均匀散点状,那更可能是随机缺失。这两种情况的处理策略差异很大:随机缺失可以考虑简单填充或者删除,结构性缺失则必须搞清楚背后的业务原因,否则填充就是在制造假数据。
我一般会配合画一张缺失值的字段间相关性图——哪些字段倾向于同时缺失。如果两个字段的缺失高度同步,说明它们可能来自同一个数据源或者同一个采集环节,修复的时候可以一起排查。这个信息在单纯的缺失率表格里是完全看不到的。
提示:缺失值热力图在样本量特别大的时候(比如几十万行)会糊成一片,这时候先随机采样几千行再画,模式依然能看出来,但渲染速度快很多。
2.2 异常值识别:箱线图看整体,散点图看个体
异常值处理是EDA里最考验判断力的环节。箱线图基于四分位距(IQR)给出异常值的统计定义——超出Q1-1.5倍IQR或Q3+1.5倍IQR的点被标记为异常。这个规则在正态分布附近的数据上表现不错,但在偏态分布上会误伤大量正常值。所以我的习惯是:箱线图用来快速定位“哪些字段有异常值”,但具体某个异常值该不该处理,必须回到散点图或者原始记录层面去看。
举个例子,一个用户消费金额字段,箱线图显示有几十个点超出上界。如果你直接按IQR规则把它们全部截断或者删除,可能会把真正的高价值用户给干掉。正确的做法是把这些异常点单独拎出来,看看它们的其他字段——如果这些用户的购买频次、客单价、活跃天数都正常甚至偏高,那它们就是真实的高价值用户,不该被当成异常处理;如果这些点的其他字段也乱七八糟,那才可能是录入错误。
散点图在异常值识别上的价值在于,它能同时展示两个维度的联合异常。有些点单独看每个字段都在正常范围内,但两个字段的组合明显偏离整体模式——这种“联合异常”只有散点图能抓出来。比如年龄和收入的关系,一个25岁的人年收入百万,单独看年龄正常、收入也在合理上限内,但组合起来就值得多看一眼。
2.3 重复值与类型错误的图形化排查
重复值和字段类型错误这两类问题,用统计表格排查效率很低,但用可视化手段可以快速定位。重复值方面,我习惯画一张重复记录数的条形图——按关键字段分组,看哪些组合出现了多次。如果重复集中在某些特定组合上,往往意味着数据采集环节有重复上报的问题,而不是简单的录入失误。
字段类型错误更隐蔽。比如一个本该是数值的字段被读成了字符串,里面混进了“未知”“暂无”这类文本,你在做数值统计的时候它会直接报错或者被静默转成NaN,但如果你没注意到,后续所有基于这个字段的分析都是错的。排查方法是:对每个字段画一张值频次条形图,取出现频率最高的前二十个值看一眼。如果数值字段的频次图里出现了非数字的字符串,一眼就能发现。这个操作看起来笨,但比写一堆类型检查代码直观得多,而且能顺带发现那些“看起来是数字但实际是编码”的字段——比如用999表示缺失、用-1表示未填答这类约定。
3. 分布形态:直方图之外你还应该看什么
3.1 直方图的分箱陷阱与核密度图的互补使用
直方图是分布可视化的第一选择,但它有个被严重低估的问题:分箱数量和起始位置会显著改变图形形态。同一批数据,分箱数取10和取50,画出来的图可能让你得出完全相反的结论。这不是直方图的缺陷,而是它的固有特性——它本质上是在做一种粗粒度的密度估计,粒度选择本身就是一种信息取舍。
我的实操习惯是:先用numpy.histogram_bin_edges配合bins='auto'让算法给一个参考分箱,然后在这个基础上上下浮动30%各画一张,对比看形态是否稳定。如果三张图的整体形态一致,说明分布结构是稳健的;如果形态差异很大,说明数据本身的结构比较微妙,需要更细致的分析。这个过程听起来繁琐,但比后面因为误判分布而选错变换方法要划算得多。
核密度估计图(KDE)是直方图的互补手段。它通过核函数对每个数据点周围做平滑,得到一条连续的密度曲线。KDE的好处是不受分箱影响,形态更平滑,适合观察分布的整体轮廓和峰的数量。但KDE也有自己的参数——带宽(bandwidth)——带宽太小曲线会过度拟合噪声出现毛刺,带宽太大又会把双峰抹平。所以我的建议是直方图和KDE叠在一起画,直方图给原始计数,KDE给平滑轮廓,两者互相印证。如果KDE显示双峰但直方图看不出来,多半是分箱没选对;如果直方图有双峰但KDE抹平了,那是带宽太大了。
3.2 箱线图与小提琴图:一个看统计量,一个看完整分布
箱线图和小提琴图经常被拿来对比,但它们的信息维度其实不一样,不是简单的替代关系。箱线图展示的是五数概括(最小值、Q1、中位数、Q3、最大值)加上异常值标记,它的优势是简洁、便于多组并排对比。小提琴图则是KDE的镜像对称版本,展示的是完整的密度分布,优势是能看到多峰、偏态这些箱线图掩盖的细节。
实际工作中我的用法是:探索初期用箱线图做快速扫描,因为它的计算和渲染都快,适合一次性看十几个字段的分布概况;确定重点字段之后,换成小提琴图做精细观察。小提琴图有个细节要注意——它默认的核密度估计在数据量少的时候(比如每组不到几十个点)会严重失真,画出一些根本不存在的“峰”。所以数据量小的时候,要么调大带宽,要么老老实实用箱线图加散点抖动(strip plot)的组合。
还有一个容易被忽略的点:箱线图在偏态分布上的表现会误导人。因为箱线图的箱体是基于四分位数的,在严重右偏的分布上,箱体会被压缩在左侧,右侧拖出一条长长的须,看起来好像“大部分数据都很小”,但实际上中位数可能并不低。这种情况下小提琴图能更真实地反映分布的“重心”在哪里。
3.3 偏态与多峰:决定后续变换方向的关键判断
分布形态的判断直接决定了后续要不要做变换、做什么变换。右偏分布(长尾在右侧)是最常见的,金额、时长、计数类字段基本都是右偏。对右偏分布,对数变换是首选,但要注意数据里不能有零或负值——如果有,得先加一个常数偏移。左偏分布相对少见,一般出现在有上限的评分类字段上,处理方式是先做反向再取对数,或者用Box-Cox变换让算法自己找最优参数。
多峰分布是更需要警惕的信号。双峰通常意味着数据里混了两个不同的群体——可能是两个渠道的用户、两个时间段的记录、两种采集设备的数据。这时候正确的做法不是急着做变换把双峰“压平”,而是先做分组,把两个群体拆开分别分析。我见过有人对双峰数据直接做标准化然后扔进模型,结果模型学到的其实是一个“群体归属”的代理特征,而不是真正的业务规律,这种模型上线后稍微换个数据分布就崩了。
判断多峰的时候,KDE的带宽选择很关键。带宽偏大会把两个靠得近的峰合并成一个宽峰,让你误以为数据是单峰的。我的经验是:先用较小的带宽画一版,看有没有多峰的迹象,如果有,再逐步调大带宽看峰是否稳定存在。真正稳健的多峰,在合理带宽范围内都应该能看出来;如果换个带宽峰就消失了,那多半是噪声。
4. 变量之间的关系:从散点图到相关性热力图
4.1 散点图的透明度、采样与六边形分箱怎么选
散点图是双变量关系可视化的主力,但它在数据量超过几千条之后就会面临严重的点重叠问题。点叠在一起,你看到的是一团色块,根本分不清哪里密哪里疏。解决手段主要有三种,各有适用场景。
第一种是加透明度(alpha)。把每个点的透明度调到0.1到0.3之间,重叠区域会因为颜色叠加而变深,密度差异就显现出来了。这个方法的优点是保留了所有原始点,不丢信息,实现也最简单。缺点是数据量特别大的时候(比如十万条以上),即使透明度很低,中心区域也会饱和成纯色,密度差异又看不出来了。
第二种是随机采样。从全量数据里随机抽几千条来画。优点是渲染快、图形干净,缺点是会丢掉稀有但重要的点——比如那些极端异常值,采样之后可能就看不到了。所以采样适合看整体关系趋势,不适合找异常。
第三种是六边形分箱图(hexbin)。把画布划分成六边形网格,每个网格的颜色深浅表示落入该网格的点数。这个方法的优点是在大数据量下依然能清晰展示密度结构,而且不会像透明度叠加那样饱和。缺点是你失去了单个点的信息,只能看到聚合后的密度。
我的选择逻辑是:数据量在几千以内,直接画散点加轻微透明度;几千到几万,透明度加降采样结合;几万以上,直接上hexbin。另外,如果分析目标是找异常值而不是看整体趋势,那不管数据量多大都应该保留全量点,宁可图糊一点也不能漏掉异常。
4.2 相关性热力图的正确读法与常见误读
相关性热力图几乎是每份EDA报告的标配,但它的误读率也高得惊人。最常见的误读是把相关系数的大小直接等同于关系强度,忽略了它只度量线性关系这个前提。两个变量可能有完美的非线性关系(比如y等于x的平方),但皮尔逊相关系数接近零。所以看到热力图上某两个变量相关性很低,不能直接下结论说它们没关系,得回到散点图上看一眼是不是非线性。
第二个常见误读是忽略相关系数的置信区间。在小样本下,一个0.5的相关系数可能完全不显著,但在热力图上它和在大样本下同样显著的0.5看起来一模一样。所以看热力图的时候,心里要清楚样本量大概是多少,样本量小的时候对中等程度的相关系数要持保留态度。
第三个误读跟异常值有关。皮尔逊相关系数对异常值极其敏感,一两个极端点就能把相关系数从0.1拉到0.8。所以画热力图之前,最好先确认参与计算的字段已经做过异常值检查,或者同时算一个斯皮尔曼秩相关系数做对比——如果两个系数差异很大,说明存在异常值或者非线性关系,这时候热力图的颜色就不能直接信了。
| 相关系数类型 | 度量对象 | 对异常值敏感度 | 适用场景 |
|---|---|---|---|
| 皮尔逊 | 线性关系 | 高 | 数据干净、近似正态 |
| 斯皮尔曼 | 单调关系 | 低 | 存在异常值或偏态 |
| 肯德尔 | 单调关系 | 低 | 小样本、有序类别 |
4.3 分组对比:把“整体规律”拆到子群体里看
辛普森悖论是EDA里最阴险的陷阱之一:在整体数据上看到的趋势,拆到子群体里可能完全反转。可视化是发现这个陷阱的最直接手段——把整体散点图按某个类别字段分组着色,如果各组的趋势方向和整体趋势不一致,那就是辛普森悖论的信号。
具体操作上,我习惯在画完整体散点图之后,立刻按最可能产生分组效应的字段(比如地区、渠道、用户等级)做分面(facet)或者分组着色。分面是把每个子群体单独画一张小图排成网格,优点是互不干扰、看得清楚,缺点是子群体多的时候图会很小;分组着色是把所有子群体画在同一张图上用颜色区分,优点是便于对比,缺点是子群体多的时候颜色不够用、点会互相遮挡。子群体数量在六个以内用分组着色,超过六个用分面。
分组箱线图也是同样的逻辑。整体箱线图显示中位数是某个值,但按类别拆开之后,各组的箱体位置可能差异巨大,甚至出现整体中位数落在两组之间的“空档”里。这种情况在用户行为分析里特别常见——把不同渠道的用户混在一起算平均活跃度,得到的数字对哪个渠道都不具有代表性。
5. 时间维度与多维结构的可视化策略
5.1 时间序列的分解视图:趋势、季节、残差分开看
只要数据里带时间戳,时间维度的可视化就必须做,而且不能只画一条原始折线了事。原始折线图把趋势、季节性、随机波动全混在一起,肉眼很难拆解。正确的做法是做时间序列分解,把这三部分拆开分别可视化。
趋势项反映的是长期走向,季节性反映的是固定周期的重复模式,残差反映的是去掉前两者之后的随机波动。拆开看的好处是:趋势项能帮你判断业务是在增长还是衰退,不受短期波动干扰;季节项能帮你确定周期长度(是周周期、月周期还是年周期),这个信息对后续做滞后特征极其重要;残差项如果出现明显的聚集或者结构性变化,说明有外部事件或者数据问题,值得单独排查。
实操上,如果数据是规则的日频或月频,直接用STL分解或者经典分解都行。如果数据是不规则时间间隔(比如事件日志),需要先重采样到固定频率再分解。重采样的时候要注意聚合方式的选择——计数类用求和,比率类用均值,不能一概而论。另外,时间序列分解对缺失值很敏感,分解之前必须先把缺失处理掉,否则分解结果会被缺失位置的插值假象污染。
5.2 平行坐标图与降维投影:多变量结构的两种看法
字段超过四五个之后,散点图矩阵就开始力不从心了。这时候有两种多变量可视化的思路:一种是平行坐标图,每个字段一根纵轴,每个样本一条折线横穿所有轴;另一种是先做降维(PCA或者t-SNE),把高维数据压到二维再画散点。
平行坐标图的优势是保留了每个原始字段的可解释性——你能直接看到某条线在哪个轴上出现了拐折,对应哪个字段的异常。缺点是样本一多线就糊成一团,而且字段的排列顺序会影响视觉上的模式识别。我的用法是:样本量控制在几百条以内,字段按相关性聚类排序,让相关的字段挨在一起,这样折线的模式更容易解读。
降维投影的优势是能把高维的聚类结构压到二维平面上直观呈现,特别适合看样本之间有没有天然的分群。但它的坑在于:PCA的投影方向是方差最大的方向,不一定是对区分群体最有用的方向;t-SNE的邻域参数对结果影响极大,不同参数下画出来的图可能完全不同。所以降维投影图只能作为探索的起点,看到分群之后必须回到原始字段上去验证是什么字段驱动了分群,不能直接拿投影坐标当特征用。
5.3 分面网格的布局原则:别让子图多到看不过来
分面(facet)是展示分组结构的利器,但用不好就是灾难。我见过一张图分了三十六个小面,每个面小得连坐标轴标签都放不下,这种分面除了证明“我会用分面”之外没有任何信息价值。
分面布局的核心原则是:子图数量控制在十二个以内,超过十二个就要考虑合并类别或者换一种展示方式。如果类别确实很多,可以先按某个指标排序,只展示头部的几个类别,剩下的归入“其他”。另外,分面网格的行列比例要跟子图的宽高比匹配——时间序列的子图适合宽扁形,所以列数应该多于行数;分布对比的子图适合接近正方形,行列数可以均衡。
还有一个细节:分面的时候各子图的坐标轴范围应该统一,否则读者无法通过视觉直接对比不同子图之间的数值大小。统一坐标轴范围可能会让某些子图的内容显得很扁,但这是值得的代价——可比性比单张图的美观更重要。如果确实需要展示某个子图的细节,可以单独把它拎出来放大画一张,而不是在分面网格里搞特殊。
6. 工具链与出图规范:让图能复用、能交付
6.1 matplotlib、seaborn、plotly的分工与取舍
这三件套的分工其实很清晰,但新手容易混着用导致代码又长又乱。matplotlib是底层引擎,控制力最强但代码最啰嗦,适合需要精细调整的场景——比如自定义图例位置、多轴布局、复杂的注释标注。seaborn是建立在matplotlib之上的统计可视化封装,它的强项是“一行代码出统计图”,而且默认样式比matplotlib好看得多,适合快速探索阶段的批量出图。plotly是交互式可视化库,适合需要悬停查看数值、缩放、筛选的场景,以及最终交付给非技术人员的报告。
我的实际工作流是:探索阶段用seaborn快速批量出图,因为改起来快、代码短;发现值得深挖的模式之后,用matplotlib精细调整出出版级质量的静态图;如果最终交付物是网页或者需要交互,再转plotly。这个流程的关键是不要在探索阶段就追求图的完美——探索阶段的图是给自己看的,能说明问题就行,把时间花在精细调整上是一种浪费。
有一个性能上的注意点:seaborn在数据量大的时候会比matplotlib慢不少,因为它内部做了很多统计计算。如果只是画原始数据的散点,直接用matplotlib的scatter更快;如果需要画带置信区间的回归线或者分布估计,那seaborn的封装就值回票价了。
6.2 配色、标注与可读性的硬性标准
图的可读性不是审美问题,是信息传达效率问题。几条硬性标准我一直在用:第一,分类配色不超过八种,超过就用同色系深浅或者直接分面,因为人眼能稳定区分的分类颜色就那么几种;第二,连续数值用感知均匀的色阶(比如viridis或者cividis),不要用彩虹色阶,彩虹色阶在数值过渡处会产生虚假的边界感;第三,坐标轴标签必须带单位,数值轴的范围不要自动裁剪到数据边界,留一点余量,否则极值点会贴在边框上;第四,图例位置不能遮挡数据,宁可把图例放到图外。
标注方面,如果图里有关键的拐点、异常点或者阈值线,一定要用文字或者箭头标出来。一张没有任何标注的图,读者需要自己猜“重点在哪”,这在报告场景里是致命的。我习惯在每张图的标题里直接写出结论——不是“用户活跃度分布图”,而是“周末活跃度较工作日下降约两成”——这样即使读者不看正文,也能从图上获取核心信息。
6.3 把EDA图变成可复用的函数库
做多了EDA你会发现,很多图是反复画的——缺失值热力图、分布对比、相关性热力图,每换一个数据集就要重画一遍。如果每次都从头写代码,效率极低而且容易漏掉之前踩过坑的细节处理。我的做法是维护一个自己的EDA绘图函数库,把常用的图封装成函数,参数化掉那些需要根据数据调整的部分(比如分箱数、透明度、分组字段)。
封装的时候有几个设计要点:函数要能接受DataFrame和字段名作为输入,内部自动处理缺失值、类型转换这些琐事;要有合理的默认参数,让调用的时候大部分情况不用传参;要返回figure对象而不是直接show,这样调用方可以继续调整或者保存。这个函数库不需要多完备,覆盖你最常画的五六种图就够了,关键是持续迭代——每次踩到新的坑,就把处理逻辑加进对应的函数里,下次就不会再踩。
注意:函数库里的默认参数要定期回顾。数据规模变了、业务场景变了,之前合适的默认值可能就不合适了。比如早期数据量小的时候默认不降采样,后来数据涨到几十万行,同样的默认参数画出来的图就糊了。
7. 几个我踩过的坑和对应的处理方式
7.1 在标准化后的数据上做分布判断
这个坑我踩过不止一次。有一次做用户消费分析,我先对金额字段做了标准化,然后画直方图看分布,发现接近正态,就按正态分布的思路设计了后续的异常检测阈值。结果上线后发现大量被标记为正常的交易其实是极端大额,回头一查原始数据,金额字段是严重的右偏长尾分布,标准化只是把尺度变了,分布形态根本没变——我看到的“接近正态”其实是标准化之后坐标轴范围变化造成的视觉错觉。
教训是:分布形态的判断必须在原始尺度上做,或者至少在变换前后各画一次对比。标准化、归一化这些操作改变的是尺度和位置,不改变分布形态,但它们在视觉上会改变直方图的外观,容易让人产生误判。对数变换才是真正改变分布形态的操作,但也要注意变换后的解读不能直接套回原始尺度。
7.2 忽略类别字段的基数导致图不可读
类别字段的基数(unique值的数量)是画图之前必须检查的。一个“城市”字段如果有三百个取值,你直接画条形图,出来的图要么标签全叠在一起,要么条形细得看不见。我早期的做法是先画了再说,结果就是反复返工。
现在的习惯是:画任何类别相关的图之前,先看基数。基数在二十以内,直接画;二十到五十,取频次最高的二十个画,其余归入“其他”;超过五十,要么做分组聚合(比如按首字母或者按区域合并),要么换一种可视化方式(比如用词云看高频项,或者直接看频次分布的帕累托图)。这个检查只需要一行代码,但能省掉大量返工时间。
7.3 把相关性当因果写进结论
这是EDA可视化里最危险的一个坑,因为它不影响图本身,但影响你从图里得出的结论。相关性热力图上两个字段颜色很深,散点图上关系也很明显,但这只能说明它们同向变动,不能说明谁导致谁。经典的例子是冰淇淋销量和溺水人数高度相关,但显然不是冰淇淋导致溺水,而是夏天这个共同因素同时推高了两者。
在EDA阶段,可视化的任务是发现关联、提出假设,不是验证因果。所以我在写任何基于可视化的结论时,都会刻意用“相关”“关联”“同向变动”这类词,避免用“导致”“引起”“影响”。如果确实需要讨论因果,那需要额外的实验设计或者因果推断方法,不是一张散点图能承担的。这个习惯看起来是措辞问题,但它能防止很多后续的决策失误——基于相关性做的干预措施,经常在真实世界里失效甚至起反作用。
7.4 图太多导致重点被淹没
最后一个坑跟图的数量有关。EDA做深入之后,很容易画出几十张图,然后全部塞进报告里。读者的注意力是有限的,几十张图看下来,真正重要的那三五个发现反而被淹没了。
我现在的做法是分两层:一层是“工作图”,自己探索用的,数量不限,怎么方便怎么画;另一层是“交付图”,从工作图里精选出五到八张,每张对应一个明确的发现,按重要性排序。交付图的标准是:每张图单独拿出来,不看正文也能传达一个完整的信息。这个筛选过程本身也是对分析结论的一次梳理——如果某张图你找不到它对应的发现,那它就不该出现在交付物里。
8. 从EDA图到建模决策的衔接
可视化做完不是终点,它的价值最终要体现在后续的建模和决策上。我习惯在EDA结束的时候做一件事:把每张关键图对应的发现,翻译成一条具体的建模动作。比如直方图发现某字段严重右偏,对应的动作是“对该字段做对数变换”;散点图发现两个字段存在非线性关系,对应的动作是“加入该字段的平方项或者分箱特征”;缺失值热力图发现某字段的缺失与目标变量相关,对应的动作是“把缺失指示变量作为一个特征保留,而不是简单填充”。
这个翻译过程能检验EDA做得是否到位——如果某张图你翻译不出任何建模动作,那要么这张图没提供有效信息,要么你还没想清楚它的含义。反过来,如果建模过程中发现某个特征表现异常,也可以回溯到EDA阶段的图上去找原因,这个双向的衔接让整个分析流程形成闭环,而不是EDA和建模各做各的。
另外,EDA阶段发现的异常值和特殊群体,在建模时要有明确的处理策略,不能视而不见。是删除、是单独建模、还是加一个标记特征,这些决策的依据都来自可视化阶段对异常值成因的判断。我在实际项目里的体会是,EDA阶段多花一天把数据的脾气摸清楚,建模阶段能省下三天来回试错的时间,而且最终模型的可解释性和稳定性都会好很多。