
前两篇聊完数据科学到底在解决什么问题、以及数学基础该怎么补之后这一篇终于进入正题。数据科学圈子里流传最广的一句话是一个数据项目真正花在建模上的时间可能只占两成剩下八成全在数据处理上。这句话一点都不夸张我在好几个项目里都遇到过类似的情况——数据采集很顺利一到清洗和转换阶段就开始失控字段对不上、格式五花八门、缺失值一大片、量纲还不统一最后模型还没开始调参人先被数据折腾麻了。这篇《数据处理 01》想做的就是把数据科学里最基础也最容易被低估的这套数据处理打法从头到尾拆开讲一遍。不管你是刚入行的数据小白还是已经写了几个月Python但总觉得处理数据时心里没底的半熟手这篇都适合你——我会把处理的层级、流程中每一步为什么这么做、以及工具链怎么选尽量用玩游戏过关的方式讲清楚。标题既然叫游戏科学那咱们就顺着这个思路来数据处理不是一个苦力活它更像一个策略游戏。你手里握着一堆原始数据就好比刚进新手村装备乱七八糟任务目标也不清晰你得先做任务规划、资源盘点再一步步把数据打磨成能上战场的形态。这一篇先解决主线任务——把从原始数据到可用数据这条路上的关键关卡全部摸清。1. 数据处理在数据科学中的真实分量它为什么值得单独开一篇很多人最开始接触数据科学往往是被机器学习、深度学习这些名词吸引过来的觉得建模、调参、出精度才是核心本事。但真正上手做过一个完整项目之后我的体会恰恰相反模型的优劣当然影响结果上限可数据处理的质量直接影响结果下限——数据是脏的、乱的、缺的再好的模型也救不回来。用一句更直白的话说模型决定你能跑多快数据处理决定你能不能上跑道。我从几个实际项目中感受特别深。一次是处理一批线上商城的历史订单表看着没多大几十万行但里面用户ID有的是数字有的是带字母的字符串时间字段一会儿是2023-07-01 14:23:11一会儿是2023/7/1 14:23还有的直接存成了时间戳价格字段里居然混进了99.00元这种带文本的值地区字段有37种写法实际上对应的省份只有15个。这些还只是字段级的杂乱等你要做用户复购分析的时候才发现同一用户因为ID格式不一致被拆成了好几个人。一个本该两三天跑完的分析光数据清洗就耗掉了一周。第二个例子是传感器时间序列数据。设备每秒钟上报一条状态记录看起来非常有规律但真实环境里网络抖动、设备重启、服务端宕机都会造成断点。如果你直接拿这份数据去做趋势预测断点前后数据会被算法误判成正常连续变化预测出来的结果自然就偏了。你必须先做时间戳对齐、做插值策略的选择、做异常跳变的识别——这些全都在建模之前发生。第三个是与热词里那些SAR数据处理TEM数据处理PET数据处理相关的思路其实是相通的。不管你是处理遥感影像、电镜图像还是医学影像RAW数据极少可以直接用都要经过辐射校正、几何校正、噪声滤除、格式转换、分辨率重采样这一套流程。很多做具体领域研究的人以为自己只是在做预处理实际上这恰恰是整个研究链条里复用性最强、最容易出成果也最容易踩坑的部分。所以我把数据处理单独拆出来成一篇文章而且计划写上几篇不是因为它高深而是因为它是数据科学中被低估得最严重的一个环节。数据科学里有一句话我觉得说得很透Garbage in garbage out。你把垃圾数据喂给模型模型只会回你一堆垃圾结论。而在真实业务里一手的数据从来都不是为你准备好的它们是为业务流程准备的你要做的就是在这套为业务而生的数据里重新建立一套为分析而生的数据视图。再加上现在围绕数据科学的热搜词里Python科学计算、NumPy、SciPy、Matplotlib、数据预处理、流式数据处理、数据处理框架这些东西被反复提及说明大家都意识到这一步的重要性但市面上的资料往往散落在各个工具文档里很少有人把处理目标—处理步骤—处理工具—常见陷阱串成一条完整的链路。这一篇就是想做这个串联工作。2. 数据处理的三个层级从能用到好用再到有用在我自己带项目的时候习惯把数据处理拆成三个层级来看。这个分层不是教科书上的标准定义是我从实践中总结出来的目的很简单——让团队里的新人知道自己的工作处在哪个环节下一步该往哪走。第一个层级是数据能用。这是最基础的也是百分之百绕不开的一步。原始数据到了手里先要解决能不能被程序正常读取的问题具体包括编码是否可以正确解析、字段名是否统一、数据类型是否正确、取值范围是否在合理空间内、重复记录是否需要去重。这个阶段处理完之后数据可以送进分析流程但还不代表分析结果是可信的。就好比你把一堆零件从仓库里搬到了工作台零件还是那些零件只是整齐了一点。第二个层级是数据好用。这个层级解决的是数据对算法是否友好。你会发现原始数据里时间戳要么是字符串要么是时间戳秒数模型没法直接理解地区字段是中文文本送到机器学习模型里前得做编码年龄和收入两个特征的数值范围差了几百倍如果直接丢给基于距离计算的算法年龄这个特征基本就被碾压了。这个阶段做的事就是特征变换、标准化、归一化、编码转换、缺失值处理、异常值处理。处理完之后的数据已经可以直接作为模型输入了而且不至于让算法因为某些特征尺度过大而产生偏倚。第三个层级是数据有用。这个层级开始带有特征工程的意味目的是创造新的信息增量。原始数据里的一个时间戳在有用层面可以拆分出年、月、日、星期、是否为节假日、是否为促销期、距离上次购买的天数等衍生特征。文本评论可以拆解成情感得分、词频向量。地理位置信息可以进一步计算距离某个核心网点的空间距离。这一步的本质是把数据从记录了什么推向这个记录能预示什么。很多时候模型精度的提升不是靠换更强的算法而是靠这个环节有没有做出真正有区分度的特征。三个层级放到一个具体的例子里面会更好理解。假设你手里有一份车辆轨迹数据GPS每隔五秒上报一个经纬度点还附带了时间、速度、方向角。如果只做到能用层级你做的事情是把纬经度格式统一、把速度里的异常0值标记出来、把时间字段解析成标准格式。到了好用层级你要处理GPS漂移剔除那些不合理的跳跃点平滑速度序列把方向角度转成弧度。到了有用层级你就开始创建特征了——相邻轨迹点之间的航向偏差是否过大、每个路段上的平均速度与限速的关系、停车点提取、OD聚类、行程切分。同样一份数据层级不同分析深度完全不同。我见过不少新手在一开始非常急拿到数据就急着跑模型跳过了前两个层级直接去追求有用——这是最容易翻车的路线。没有清洗干净的数据会注入隐藏偏差没有标准化的特征会让模型权重解释失真没有做缺失值策略会让样本量忽大忽小导致每一次跑出来的结果都不一致。所以这一篇里我着重要讲的就是前两个层级第三个层级会在特征工程的部分再深入展开。3. 通用数据处理的完整链路每个环节在做什么、为什么非做不可脱离具体场景谈数据处理容易变成空话。这一节我带大家走一遍我认为最标准的处理链路并把我踩过的每一个坑都标出来。这里的顺序不是随便排的每个环节都有它的上下游逻辑。3.1 环节一探索性理解——先读数据字典再读数据本身这一步很多新手会跳过但恰恰是省时间的关键。拿到一张表或者一堆文件先在脑海里建立一个问题清单每一列的业务含义是什么取值类型是什么合法取值范围是多少有缺失的话缺失比例大概多大这些在拿到数据时可以快速用Pandas跑一遍但这里先不展开工具篇会说。我的习惯是先看数据字典没有数据字典就看字段名和样例数据再决定后续的清洗策略。这里有一个非常实用的动作用describe和info一类的函数快速了解数据的分布形态和空值情况同时看一眼每列的唯一值数量。唯一值数量如果远小于行数这一列大概率是类别型如果几乎等于行数那大概率是ID或连续型。这个判断会影响你后续选编码方式和缺失值填充策略。3.2 环节二字段与编码统一——最枯燥但最救命的一步第二个环节解决的是同一个东西叫法不一致的问题。只要数据源超过一个这件事几乎必然发生。用户表里叫user_id订单表里叫uid日志表里叫memberId这三个字段是同一个东西但你如果直接join程序不会认。再比如性别字段有的表存1和2有的表存M和F有的表存男女有的表甚至存了未知。还有编码问题这在读取CSV文件时特别常见——从某个老系统导出的文件是GBK编码你用默认UTF-8去读满屏乱码。这一环我的固定做法是先建立字段映射表把所有表里的同类字段归到一个标准命名下再统一分类值的编码规范最后检查文件编码必要时做编码转换。这里要特别提醒一句——字段映射表一定要留文档不然三个月后你自己回来看代码根本想不通当时为什么把A表某个字段设成了这种规则。3.3 环节三缺失值处理——先判断缺失机制再选策略这是整个数据处理里最需要动脑子的一环因为缺失值不是简单的填上就完事不同的缺失机制对应完全不同的处理策略。我从统计学角度打个比方如果数据是完全随机缺失比如说用户填问卷时随机漏填了一道题那你用均值填充是比较安全的如果是随机缺失比如收入高的人更不愿意填收入那么缺失本身就携带了信息你需要用一个能够捕捉是否缺失这个信息的模型来填补或者干脆把是否缺失单独做成一个特征如果是非随机缺失比如超过设定阈值的那些极端值直接被系统过滤掉了那你几乎无法从数据内部自行修复只能回到业务源头去核实。实操里我的策略优先级是这样的能删则删的只限于缺失比例极低一般低于1%—2%且该列重要性不高的情形缺失比例在可控范围内并且是数值型连续变量用中位数填充比均值更稳健因为均值对异常值敏感时间序列数据优先用前后值插值或线性插值而不是全局填充因为序列局部变化趋势是有意义的类别变量优先用众数或者单独一个缺失类如果这一列对未来模型很重要而缺失比例又高千万别硬填——把是否缺失本身做成一个特征往往更有效。3.4 环节四异常值识别——不是所有异常都要删异常值这一步很有意思因为它是最需要结合业务背景的一环。单纯从统计学角度看价格字段出现了一个比均值高几千倍的值显然是异常但从业务上看这可能是公司真实卖出的一个企业级订单金额就是这么大。所以我的原则是先识别再分级最后才决定处理方式而不是一见到异常就删掉。常用的识别方法我放在第4节详细讲这里先给一个思路——不管用什么方法识别出来之后先把它们单独拉出来看一遍确认是录入错误测量噪声还是真实但极端的业务行为。录入错误改数据测量噪声平滑处理真实极端值保留但后续建模时要考虑是否单独建模或做截尾处理。机器学习中的数据处理里经常提到一个概念叫健壮性它的前提就是你在预处理阶段没有武断地把信息抹掉。3.5 环节五重复数据处理——别把去重想得太简单重复数据看着简单真做起来全是细节。首先是什么是重复的定义问题。两行数据所有字段完全一致这是最简单的情况直接drop掉就可以。但更常见的是部分字段重复——同一用户ID出现了两次但购买记录不同这根本不是重复行这是同一个用户的多次行为。如果同一个用户ID同时存在两条注册信息且两条的注册时间不同你就得判断哪一条是有效的。另一个坑是时间窗口内的重复——同一台设备同一分钟内上报了三条状态相同的数据这在传感器数据里很常见属于系统重传导致的冗余可以通过时间粒度和内容双重判定去做去重。我的处理顺序是先按照业务主键识别唯一性再在此基础上检查完全重复字段最后考虑时间窗口模糊去重。每一步都要记录删了多少行原因是什么方便后面回溯。3.6 环节六数据变换与标准化——让不同尺度的特征站到同一平面这环节是给数据好用层级兜底的。两个经典操作是标准化和归一化。标准化是把数据变成均值为0、标准差为1的分布公式是(x - μ) / σ归一化是把数据压缩到[0,1]区间公式是(x - min) / (max - min)。共性在于它们都解决量纲不同导致算法偏心的问题。适用场景也不同如果后续算法假设数据服从正态分布或者涉及梯度下降优化标准化通常更好如果后续算法是基于距离计算的但你不希望离群点主导min-max区间用标准化更稳健如果只是把特征范围对齐到固定区间比如图像像素从[0,255]映射到[0,1]归一化就够了。学科学计算的时候这个环节就是NumPy和SciPy的主战场——向量化做标准化、快速的百分位计算、通过正态分布检验来选择变换方式都是基本功。不过这里先卖个关子细节我会放到第四篇讲Python工具实战的时候挨个演示。4. 简单有效的数据体检实战用最小代码快速摸底理论讲了一堆很多人可能已经在心里嘀咕道理我都懂但拿到一份数据之后第一步到底应该敲什么代码这一节我专门给一个可以直接抄作业的数据体检流程用的是Python生态里最主流的三件套——Pandas、NumPy、Matplotlib。学过Python科学计算和数据科学应用的人对这三件套肯定不陌生它们各司其职Pandas负责表格操作NumPy负责数值计算Matplotlib负责可视化。先说一个我反复强调的观念数据体检的核心不是跑出几张统计表而是对数据形成空间感。所谓空间感就是你知道每一列大概是什么分布、有没有明显异常、列和列之间有没有可疑关系。我的体检代码通常由四步组成。第一步是看一眼输出前几行数据确认读取是否正常、字段名是否符合预期、样例数据是不是人话。第二步是盘一遍查看每列的数据类型、非空数量、唯一值数量。第三步是算一笔对数值列输出描述性统计重点看min、max、mean、std和几个分位数。这里我有一个经常叮嘱新人的点——不要只看均值一定要看25%分位数和75%分位数之间的距离也就是IQR它能比均值更真实地反映数据的集中趋势。第四步是画一画对每一列数值特征做直方图对可疑列做箱线图对两两关系做散点矩阵。这一步是Matplotlib的主场很多人把画图当作出报告时的事但我的习惯是先画给自己看再做处理。一张直方图能在一秒内告诉你这一列是正态、长尾还是双峰这比任何统计检验都直观。具体到代码层面示意大概是这样的import pandas as pd import numpy as np import matplotlib.pyplot as plt df pd.read_csv(raw_data.csv, encodingutf-8) # 第一步看一眼 print(df.head()) # 第二步盘一遍 print(df.info()) print(df.nunique()) # 第三步算一笔 print(df.describe(percentiles[0.01, 0.25, 0.5, 0.75, 0.99])) # 第四步画一画 numeric_cols df.select_dtypes(include[np.number]).columns for col in numeric_cols[:6]: df[col].hist(bins50) plt.title(col) plt.show() df.boxplot(rot90) plt.show()整个过程跑下来你对这份数据的体感基本就建立起来了后续做清洗的时候心里有谱。我在实际项目里发现这四步做完至少能提前发现一半以上的数据陷阱比如某列数值为什么有负数、某列的99%分位数和max差了上百倍、某两列之间出现了完全等比例的同涨同跌关系。这些线索比任何教科书里的完美数据都更有分析价值。如果说上面这些是在局外人视角下的建议那么再补一句我在真实项目里强调过无数遍的话不要急着写清洗逻辑先用这份体检把数据质量报告写出来。报告里不需要华丽的词汇只需要把每一项发现列清楚——哪种字段有多少比例缺失、哪个字段的取值范围异常、哪些字段可能是同一个信息的多种编码形态。这份报告的价值在于它让你的数据处理变成一个有据可依的过程而不是看到哪里改哪里。5. 数据处理框架与场景选择什么时候该用Pandas什么时候该上Spark随着项目规模从小到大我越来越意识到一个问题很多人在一开始就陷入工具选择的焦虑——文件一大就想着是不是该上大数据框架数据一复杂就想着是不是该换掉Pandas。其实工具选择的核心逻辑只有一条你的数据规模和计算复杂度是否已经超出了单机内存的舒适区。Pandas能处理的数据量在多数业务分析场景下完全够用。说个大概的量级感受单机上处理几百万行、几十列的结构化数据如果逻辑不复杂Pandas都能在一分钟到几分钟内完成内存占用看列数和数据类型一般几个GB以内都是可以接受的。只有当数据量达到数亿行、数据源分布在多个节点、计算需要分布式并行的时候才有必要考虑Spark之类的数据处理框架。很多初学者容易陷入我数据有100GB是不是必须用Spark的误区但实际上100GB的数据如果其分析只需要某几列那可以先做列裁剪把内存占用砍到一个很小的量级问题就解决了。另外还有一个场景差异是流式数据。热词里提到了流式数据处理这跟离线批处理是两类玩法。流式数据处理的特点是数据持续到达、实时计算、窗口聚合经典工具比如Kafka配合Flink或Spark Streaming。如果你做的是传感器实时日志监控、实时交易风控这类场景你需要的是流处理思维——对每一个到达的事件做增量状态更新。但绝大多数刚入门的人实际上做的是离线分析数据是一次性拿到一批的这时候老老实实把批处理做好远比硬套流处理框架有价值。我在带新人时经常听到有人说我们数据是实时产生的所以要用流式框架但仔细一问他们的业务需求其实是每天凌晨跑一次离线报表这就属于典型的需求和工具错配。用一张表格把几个常见场景对应一下场景数据规模典型工具核心关注点小规模结构化表百万行以内Pandas / NumPy / SciPy清洗、变换、可视化探索中大规模结构化表千万至上亿行Pandas 分块处理 / Dask内存管理、列裁剪、分块聚合分布式大数据数亿行以上Spark分布式任务调度、数据分区流式实时数据持续到达Kafka Flink / Spark Streaming窗口计算、状态管理、延迟科学计算数组多为数值矩阵NumPy / SciPy向量化运算、线性代数、信号处理这个表格只是给大家一个初步印象不是为了让你背下来。真实情况里Pandas和Spark常常配合使用——先用Spark在大规模数据上做粗粒度的过滤和聚合把结果收缩到Pandas可以处理的规模再用Pandas做精细的清洗和特征处理。这种大框架粗加工、小工具精加工的组合打法在工业界非常常见。再说一个很容易被人忽略的点工具选择还要考虑团队协作。如果你的数据处理流程要交给别人review或者要跑在别人的机器上尽量选择生态成熟、文档齐全的工具。Pandas和Spark之所以能成为主流不是因为它俩在所有方面都最优而是因为背后的社区生态足够大遇到问题你总能在网上找到别人的解决方案。这本身就是一种隐形生产力。6. 我踩过的高频数据坑从编码错乱到浮点误差的完整记录最后这个部分是把我在多个项目里踩过的、并且有一定普适性的坑集中列出来。这些坑不是那种很高深的Bug恰恰是那些看起来不起眼、却能让结果整体跑偏的细节问题。第一个坑文件编码不统一。这个坑我在第3节提到过这里展开说。当一个数据集来自多个系统时最常见的组合就是一部分文件是UTF-8编码一部分是GBK编码。你用Pandas统一用UTF-8去读GBK文件会报解析错误就算不报错某些特殊字符也会变成乱码。我之前在一个项目里就是因为有个字段里的繁体字被错误解编码导致后面做文本分类时这个字段整列变成了无意义的符号序列。解决办法很笨但很有效在读取文件之前先写一个编码检测函数对每个文件做一次探测如果你的文件是可以统一转换的那就全部先转成UTF-8再进后续流程。编码统一这件事属于不做没事做了保命的类型。第二个坑浮点数比较。这条看起来像是编程常识但实际犯错的频率远超想象。当你判断某个数值字段是否等于0、或者某个比率是否达到0.1时直接用比较往往会出现意想不到的结果。原因在于浮点数在计算机内部并不是精确的十进制表示0.1 0.2的结果实际上是0.30000000000000004。我的处理原则是凡是浮点数之间做等值判断一律用绝对误差或者相对误差来判断凡是需要高精度计算的金额类字段尽量用整数类型去存储最小货币单位。第三个坑时间字段的隐藏时区。时间数据最麻烦的还不是格式五花八门而是时区不一致。多个源系统可能部署在不同的服务器上服务器时间的设置又未必统一。如果数据是给国内业务用的你看到的2023-07-01 14:23:11到底是北京时间还是UTC时间可能你自己都说不清。我建议的处理方式是在数据处理的一开始就把时间列统一转换成带时区信息的时间戳再根据后续分析需求转成需要的时区。这个操作一步到位但很多人往往是做到时间对比对不上的时候才回头补那时候成本就高了。第四个坑批量处理时一个文件一个样。这是所有系统性坑里最恶心的一个。你有几千个文件要合并前十个文件的列顺序完全一致你写了一个循环一切顺利结果跑到第1000个文件突然发现它的某一列从字符串变成了数字类型或者列顺序反了。Pandas在读取时会自动推断列的类型所以同一个业务字段在不同文件里可能被推断成不同数据类型。为了避免这个问题我在读取多文件时通常显式指定dtype参数把每个字段的类型固定下来宁可让程序报错也不让它聪明地自动转换。第五个坑没有记录操作日志。数据处理流程一旦复杂起来你会面临一个非常尴尬的问题别人问你这个字段清洗规则是什么、这些行为什么被删掉了你答不上来。我在早期项目里吃过这个亏后来形成了自己的规范——每一次数据变换操作之后都输出一行摘要记录操作前后行数、列数、受影响字段、以及删改原因。这个习惯看起来多花了几分钟时间但在数据出问题需要回溯时能帮你省下几个小时甚至一整天。这几个坑不是孤立的它们往往会在同一个项目里连着爆发。比如你先遇到编码问题处理完编码后又发现类型变了转完类型又发现时间对不上一路打怪打到怀疑人生。但也只有经历过这一轮你对数据处理的敬畏感才会真正建立起来。我自己的体会是做数据处理时间越久越不敢在拿到数据的第一时间就写分析代码——先体检、再清洗、留好日志这套流程虽然看起来慢但整体的稳定性很高。这也是为什么我会把这一篇定位成游戏科学系列里的重要一章通关的秘诀从来不是操作快而是每一步都走得稳。后续我把Python科学计算三件套的实操细节、特征工程的完整方法论、以及不同数据类型时间序列、文本、图像、地理数据的处理专场一篇篇慢慢填上。