INT303 Big Data Analysis 大数据分析 Pt.4 数据预处理:从原始混沌到分析就绪
1. 数据预处理:从“脏乱差”到“高富帅”的必经之路
如果你做过数据分析,肯定有过这样的体验:兴冲冲地拿到一份数据,准备大展拳脚,结果打开一看,瞬间傻眼。数据里到处都是空白,有些数字大得离谱,有些文本乱码一堆,格式更是五花八门。这种感觉,就像你准备做一桌满汉全席,结果发现食材里混着烂菜叶、发霉的肉和没洗的泥巴。数据预处理,就是那个帮你把“烂菜叶”挑出来、把“泥巴”洗干净、把食材切配整齐的“厨房预处理”环节。
在INT303大数据分析的课程里,我们之前聊了数据挖掘的宏观流程。今天,我们就来深挖其中最核心、最耗时,也最容易被新手忽略的一环:数据预处理。它绝不仅仅是“洗数据”那么简单,而是一个系统性的工程,目标是把原始混沌的数据,变成分析就绪的干净数据集。我见过太多项目,算法选得再高级,模型调得再精细,最后效果却一塌糊涂,追根溯源,八成是数据预处理没做好。可以说,预处理的质量,直接决定了你后续所有分析工作的上限。
为了让大家有更直观的感受,我们贯穿全文用一个具体的电商场景来拆解:假设你在一家电商公司,手头有海量的、杂乱无章的原始数据,包括用户每一次点击、浏览、搜索、加购、下单的记录,以及商品信息、评论等等。你的任务,是为精准推荐模型准备好“弹药”。下面,我们就一步步看看,如何把这些“原始矿石”冶炼成“高纯度钢材”。
2. 数据预处理的三大核心任务
原始文章把预处理分成了减少数据量、数据清洗和特征工程几块。在实际项目中,我觉得可以更聚焦地理解为三个递进的目标:把数据变小、把数据变干净、把数据变聪明。这三步走稳了,你的数据才算真正上了跑道。
2.1 第一步:把数据变小——采样与降维的艺术
面对TB甚至PB级的数据,你不可能也没必要把每一行都喂给算法。第一步就是科学地缩减数据规模。这里有两个核心武器:采样和降维。
采样,说白了就是“挑代表”。但不是瞎挑。还记得原始文章里那个伦敦人身高的例子吗?如果你只在大学校园里抽样,得到的平均身高很可能无法代表整个伦敦市民。这就是采样偏差。在电商场景里,如果你只采样工作日的白天数据,就会完全忽略“夜猫子”用户和周末的消费高峰,导致模型无法理解全貌。
我常用的几种采样策略,各有各的适用场景:
- 简单随机抽样:最基础的方法,就像抽签,每个用户被选中的概率相同。适合数据分布比较均匀的时候。用Python的Pandas很容易实现:
import pandas as pd
# 假设df是你的原始DataFrame
sampled_df = df.sample(n=10000, random_state=42) # 随机抽取1万条,random_state保证可复现
- 分层抽样:这是处理不平衡数据的利器。比如我们的用户行为数据里,下单用户可能只占浏览用户的5%。如果随机抽,下单样本太少,模型就学不会“下单”这个关键动作。这时,我们就应该分别从“下单用户”和“未下单用户”两个池子里按比例抽样,确保两者都有足够的代表。原始文章里信用卡欺诈交易的例子也是这个道理。
- 时间加权抽样:对于行为数据,近期数据往往比远古数据更有价值。我们可以设计一个概率函数,让数据被抽中的概率随着时间衰减。比如,让昨天数据的权重是上个月的10倍。这在预测用户下一单买什么的时候特别有用。
降维,则是给数据“瘦身”。想象一下,你的商品有上万个属性:颜色、尺寸、材质、风格、季节……这就是上万个维度。很多维度可能是冗余的(比如“风格”和“设计师”高度相关)或者无关的(比如“商品入库批次号”对推荐毫无影响)。降维就是找出那些真正有信息量的核心维度。
最经典的方法是主成分分析(PCA)。它就像给你的数据找一个“最佳拍照角度”,把原来散乱的高维数据,投影到几个最重要的“方向”(主成分)上,用更少的维度保留最多的信息。在电商场景,我们可以用PCA把用户对成千上万个商品类目的浏览偏好,压缩成几个“兴趣因子”,比如“实用主义因子”、“时尚潮流因子”、“性价比因子”。这样,算法处理起来就轻松多了。
2.2 第二步:把数据变干净——数据清洗的实战细节
数据变小了,接下来就得“洗澡”。数据清洗是体力活,也是细致活,直接关系到分析的“地基”稳不稳。主要对付四大“脏东西”:缺失值、异常值、噪声和不一致。
处理缺失值,原始文章提到了几种方法:删除、用均值/中位数填充、用最近邻填充。我结合电商数据说说怎么选。
- 直接删除:最简单粗暴。如果某个用户的“性别”、“年龄”等关键字段都缺失,而且这样的用户比例很小(比如<1%),直接删掉对整体影响不大。但如果缺失率很高,比如30%的商品没有“上架时间”,盲目删除会损失大量信息。
- 统计值填充:对于数值型特征,比如“商品价格”,如果少量缺失,用中位数填充往往比均值更稳健,因为中位数不受极端值影响。对于“用户年龄”,用众数(出现最多的年龄)填充可能比均值更合理,毕竟年龄分布可能不是正态的。
- 模型预测填充:这是更高级的方法。比如,用其他完整的字段(如浏览品类、消费金额)去训练一个回归模型,来预测缺失的“用户年龄”。这相当于让数据自己“猜”自己,准确性更高,但计算也更复杂。
- 实战建议:对于关键特征,不要只用一种方法。可以尝试多种填充策略,分别跑一下模型,看哪种效果最好。同时,一定要增加一个指示特征,比如“年龄_是否缺失”,这是一个二值特征(1代表缺失,0代表不缺失)。缺失本身可能就是一种信息(比如不愿填写年龄的用户可能有特定的行为模式),这个特征能帮助模型捕捉到这一点。
揪出异常值,就是找出那些“不合群”的数据点。在电商数据里,这可能是一个用户一秒内点击了1000次(可能是爬虫),或者一笔订单金额高达一个亿(可能是测试数据或错误)。
- 3σ原则(三西格玛原则):对于近似正态分布的数据,可以认为99.7%的数据落在均值上下3个标准差的范围内。超出这个范围的,可以视为异常值。用Python排查很简单:
import numpy as np
mean_val = df['order_amount'].mean()
std_val = df['order_amount'].std()
# 找出异常值索引
outliers = df[np.abs(df['order_amount'] - mean_val) > 3 * std_val]
- 箱线图法(IQR):我更推荐这个方法,因为它不依赖于数据服从正态分布。它通过四分位数来定义异常值边界,非常直观。
- 处理方式:发现异常值后,别急着删!先分析原因。如果是明显的错误(如负数的价格),直接修正或删除。如果是真实但极端的数据(比如某个土豪一次性买了100台手机),这类“业务异常值”可能蕴含着高价值用户的信息,不应该删除,而是可以考虑单独建模或进行对数变换来平滑其影响。
统一数据格式,这是最琐碎但也最容易出问题的一步。比如日期,有的记录是“2023-12-01”,有的是“01/12/2023”,还有的是“20231201”。必须统一成一种格式(如时间戳),后续才能进行时间序列分析。再比如商品类目,“手机”和“智能手机”可能指的是同一个东西,需要做标准化,统一成“手机”这个名称。
2.3 第三步:把数据变聪明——特征工程的魔法
干净的数据是“素颜”,特征工程就是“化妆+造型”,让数据的内在价值最大化地展现给模型。这是最能体现数据科学家功力的地方。
特征提取:从原始数据中创造新的、更有意义的特征。在电商场景里:
- 从“用户浏览时间戳”可以提取出“是否周末”、“是否节假日”、“一天中的时段(早晨/午后/夜晚)”。
- 从“用户行为序列”可以提取出“近7天登录天数”、“平均每次会话浏览商品数”、“最后一次购买距今天数”。
- 从“商品文本描述”中,可以用TF-IDF或词嵌入提取出关键词向量,来判断商品风格是“简约”还是“奢华”。
特征变换:为了让模型更好地学习,我们经常需要改变特征的分布。
- 归一化/标准化:这是必做项。如果你的特征“用户年龄”范围是18-70,而“月消费金额”范围是0-50000,模型会天然地更关注金额大的特征。通过Min-Max归一化(缩放到[0,1])或Z-score标准化(均值为0,标准差为1),可以让所有特征站在同一起跑线上。原始文章对此有详细公式,我这里强调一下选择:如果数据分布有明显边界(如百分比、评分),用归一化;如果数据分布没有明显边界或存在异常值,用标准化更稳健。
- 非线性变换:对于严重偏态的数据,比如用户消费金额,大部分用户花得少,少数用户花得极多。直接建模,模型会被少数极端值带偏。这时,对其取对数(log) 是一个神奇的技巧,它能将数据的分布拉得更接近正态分布,让模型学得更舒服。
特征选择:不是特征越多越好。冗余的特征会增加计算负担,还可能引入噪声,导致模型过拟合。我们可以通过计算特征与目标变量的相关性、使用树模型(如随机森林)看特征重要性,或者用递归特征消除(RFE)等方法,筛选出最重要的特征子集。在电商推荐里,“用户ID”这种高基数特征,如果不加处理直接入模,基本就是灾难,通常需要将其转化为“用户历史行为统计”这类低维稠密特征。
3. 电商场景实战:从日志到特征矩阵
现在,我们把上面的理论套进开头的电商公司场景,走一遍完整的预处理流水线。假设我们拿到了过去一个月原始的用户行为日志(user_behavior_logs.csv)和商品数据表(product_info.csv)。
3.1 原始数据长什么样?
用户行为日志可能包含这些字段:user_id, item_id, behavior_type(click, cart, buy), timestamp, category_id。
商品表可能包含:item_id, title, price, category, tags。
原始数据的问题一眼就能看出来:行为日志里同一个用户有成千上万条记录;商品标题是杂乱的非结构化文本;价格单位可能不统一(有的带“元”,有的不带);timestamp是难以直接计算的时间戳字符串。
3.2 第一步:数据清洗与集成
首先,我们把两张表通过item_id关联起来。
import pandas as pd
# 读取数据
logs_df = pd.read_csv('user_behavior_logs.csv')
product_df = pd.read_csv('product_info.csv')
# 合并数据
merged_df = pd.merge(logs_df, product_df, on='item_id', how='left')
接着,处理缺失值。检查发现有些商品因为下架,在商品表里没有信息,导致合并后category等字段为空。对于这类记录,如果比例小(比如<0.5%),我们可以直接删除。如果比例大,可能需要用“未知”类别填充,或者利用日志中的category_id去反推大类。
然后,处理异常值。检查price字段,发现有几个商品价格是0或999999。经查,0元的是秒杀活动商品,是合理的;999999的则是测试数据,需要过滤掉。
# 过滤掉价格异常高的测试数据
cleaned_df = merged_df[merged_df['price'] < 100000]
最后,统一格式。将timestamp转换为datetime格式,并提取出新的时间特征。
cleaned_df['event_time'] = pd.to_datetime(cleaned_df['timestamp'], unit='ms')
cleaned_df['hour'] = cleaned_df['event_time'].dt.hour
cleaned_df['is_weekend'] = cleaned_df['event_time'].dt.weekday >= 5
3.3 第二步:用户行为序列的聚合与采样
原始日志是“粒度”最细的流水记录。为了分析用户偏好,我们需要将其聚合到用户粒度。我们计算每个用户的核心行为指标:
user_features = cleaned_df.groupby('user_id').agg(
total_clicks=('behavior_type', lambda x: (x=='click').sum()),
total_carts=('behavior_type', lambda x: (x=='cart').sum()),
total_purchases=('behavior_type', lambda x: (x=='buy').sum()),
avg_price_viewed=('price', 'mean'),
favorite_category=('category', lambda x: x.mode()[0] if not x.mode().empty else 'unknown'),
last_active_time=('event_time', 'max')
).reset_index()
现在,我们有了一个以user_id为主键的特征宽表。如果用户量巨大(例如上亿),我们需要对这个宽表进行分层抽样。比如,按照“是否购买过”(购买/未购买)进行分层,确保训练集中正负样本均衡。
3.4 第三步:商品文本特征工程
商品标题是宝贵的非结构化数据。我们使用TF-IDF来提取关键信息。
from sklearn.feature_extraction.text import TfidfVectorizer
# 预处理文本:这里简单处理,实际中可能需要分词、去停用词等
product_df['title_cleaned'] = product_df['title'].str.lower().str.replace(r'[^\w\s]', '', regex=True)
# 初始化TF-IDF,并设置最大特征数为1000
tfidf = TfidfVectorizer(max_features=1000, stop_words='english')
title_tfidf_matrix = tfidf.fit_transform(product_df['title_cleaned'])
# 将稀疏矩阵转换为DataFrame,并合并回商品表
title_tfidf_df = pd.DataFrame(title_tfidf_matrix.toarray(), columns=tfidf.get_feature_names_out())
product_df = pd.concat([product_df, title_tfidf_df], axis=1)
这样,每个商品就被表示成了一个1000维的向量,向量中的每个值代表了某个词(如“智能手机”、“防水”、“新款”)在这个商品标题中的重要程度。
3.5 第四步:特征缩放与编码
现在,我们手上有数值特征(如avg_price_viewed)、分类特征(如favorite_category)和已经向量化的文本特征。
- 对数值特征
avg_price_viewed进行标准化,因为价格分布可能不是均匀的。 - 对分类特征
favorite_category进行独热编码(One-Hot Encoding),将其转换为模型可理解的0/1向量。 - 文本TF-IDF特征已经是数值型,且经过了TF-IDF本身的加权,通常不需要再次缩放。
做完这一切,我们终于得到了一个干净、规整、富含信息的特征矩阵。每一行代表一个用户,每一列代表一个特征(包括行为统计、偏好类目、价格敏感度、感兴趣的标题关键词等)。这个矩阵,才是真正可以喂给推荐算法(如协同过滤、深度学习模型)的“分析就绪”数据。
4. 避坑指南:预处理中常见的“雷区”
踩过不少坑之后,我总结了几条血泪教训,希望能帮你绕过去。
第一坑:顺序颠倒,边洗边丢。 千万不要在数据合并、关联之前就匆忙做删除操作。比如,你先清洗了用户表,删掉了一些“异常”用户,然后又去关联行为日志,结果发现很多日志找不到对应的用户,导致信息丢失。正确的顺序永远是:先集成(把相关表连起来),再清洗。
第二坑:泄露“天机”,穿越未来。 这是特征工程中最致命的错误。比如,你用“用户未来一年的总消费额”作为特征,去预测“用户下个月是否会消费”,这模型效果当然好,因为答案已经写在特征里了。在划分训练集和测试集时,必须严格按照时间顺序,只能用历史数据生成特征,去预测未来。确保特征在生成时,绝对无法接触到它要预测的“未来”信息。
第三坑:盲目跟风,乱用高级方法。 不是所有数据都适合做PCA降维,如果特征之间相关性很低,PCA效果就不好。也不是所有偏态数据都要取log,对于包含0值的数据,取log前需要加一个很小的常数(如1),否则会出错。理解每种方法的前提假设和适用场景,比学会调用API更重要。
第四坑:不做记录,黑盒操作。 预处理过程中每一个决策,比如“删除了价格>100万的记录”、“用中位数填充了30%缺失的年龄字段”、“对消费金额做了log(x+1)变换”,都必须详细记录。这不仅是项目可复现性的要求,更是当你模型效果不好时,进行回溯排查的唯一依据。我习惯用一个preprocessing_log.md文件来记录所有步骤和参数。
数据预处理没有一成不变的“金科玉律”。它更像一门手艺,需要你在理解业务、理解数据、理解算法的基础上,不断尝试和调整。一开始可能会觉得繁琐,但当你看到经过精心预处理的数据,让模型性能大幅提升时,那种成就感是无与伦比的。记住,高质量的数据是高质量分析的唯一前提,在这上面花再多时间都值得。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)