1. 从脏数据到干净数据:电商销售数据的清洗实战
刚拿到手的电商销售数据往往像一团乱麻——缺失值、异常值、重复记录、格式混乱等问题比比皆是。我最近处理过一个典型数据集,原始CSV文件打开后是这样的:
import pandas as pd raw_data = pd.read_csv('ecommerce_sales.csv') print(raw_data.head(3)) # 输出示例: # Order ID Customer ID Order Date ... Quantity UnitPrice TotalPrice # 0 1001.0 CUST001 2023/5/1 ... 2.0 $29.9 $59.8 # 1 NaN NaN NaN ... NaN NaN NaN # 2 1002.0 CUST002 5/3/2023 ... 1.0 19.9 NaN数据清洗四部曲是我的标准处理流程:
- 处理缺失值:先用
isnull().sum()统计每列缺失情况。对于关键字段(如订单ID)缺失的行直接删除,数值型字段用中位数填充,分类字段用众数或"Unknown"标记。
# 删除关键字段缺失的行 clean_data = raw_data.dropna(subset=['Order ID', 'Customer ID']).copy() # 填充数值型缺失值 clean_data['Quantity'] = clean_data['Quantity'].fillna( clean_data['Quantity'].median())- 统一数据格式:日期列经常是重灾区,需要用
pd.to_datetime统一格式。价格字段中的货币符号也需要去除并转为数值类型。
# 统一日期格式 clean_data['Order Date'] = pd.to_datetime( clean_data['Order Date'], errors='coerce') # 处理价格字段 clean_data['UnitPrice'] = clean_data['UnitPrice'].str.replace('$', '').astype(float)- 处理异常值:通过描述统计和箱线图发现异常,比如数量为负数或单价过高的记录。我常用
query()方法过滤:
clean_data = clean_data.query('Quantity > 0 and UnitPrice < 1000')- 去重处理:最后用
drop_duplicates()去除完全重复的记录,对于业务关键字段组合重复的记录需要根据业务规则处理。
# 按订单ID去重,保留最后一条记录 clean_data = clean_data.drop_duplicates( subset=['Order ID'], keep='last')处理后的数据质量显著提升,可以进入下一阶段。记得用to_csv()保存清洗结果,并记录所有处理步骤——这是数据工程师的好习惯。
2. 探索性分析:发现数据中的黄金
清洗后的数据就像未经雕琢的玉石,探索性分析(EDA)就是我们的雕刻刀。我习惯从三个维度入手:
维度一:基础统计特征
# 快速获取数值型字段统计 print(clean_data.describe()) # 分类字段分布 print(clean_data['Product Category'].value_counts(normalize=True))维度二:时间趋势分析
# 按周统计销售额趋势 weekly_sales = clean_data.set_index('Order Date')['TotalPrice'].resample('W').sum() weekly_sales.plot(title='Weekly Sales Trend');维度三:多维交叉分析
# 使用pivot_table分析各品类在各地区的销售表现 pd.pivot_table(clean_data, values='TotalPrice', index='Product Category', columns='Region', aggfunc=['sum', 'count'])最近一个项目中,通过EDA我发现:
- 周末的客单价比工作日高35%
- 某品类在华东地区销量异常偏低(后来发现是物流问题)
- 20%的用户贡献了80%的销售额
这些发现直接影响后续的运营策略。记住:好的EDA不仅要发现已知模式,更要揭示未知问题。
3. 特征工程:为模型注入灵魂
特征工程是建模成功的关键。对于电商数据,我通常会构造以下几类特征:
时间特征:
clean_data['Order_DayOfWeek'] = clean_data['Order Date'].dt.dayofweek clean_data['Is_Weekend'] = clean_data['Order_DayOfWeek'] >= 5用户行为特征:
# 计算用户购买频次 user_stats = clean_data.groupby('Customer ID').agg( Purchase_Freq=('Order ID', 'count'), Avg_Spend=('TotalPrice', 'mean') ) clean_data = clean_data.merge(user_stats, on='Customer ID')产品关联特征:
# 计算品类平均价格 category_avg = clean_data.groupby('Product Category')['UnitPrice'].mean() clean_data['Price_Ratio'] = clean_data['UnitPrice'] / clean_data['Product Category'].map(category_avg)高阶特征:
# 使用rolling计算移动平均 clean_data['7D_Avg_Spend'] = clean_data.groupby('Customer ID')['TotalPrice'].transform( lambda x: x.rolling(7, min_periods=1).mean())特征构造后一定要做标准化/归一化处理:
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler() clean_data[['Avg_Spend', 'Price_Ratio']] = scaler.fit_transform( clean_data[['Avg_Spend', 'Price_Ratio']])好的特征应该像好调料——能让模型的"味道"更鲜美。我曾通过构造"用户价值分位数"特征,将RF模型的AUC提升了0.15。
4. 建模准备:数据分割与验证策略
进入建模前,还需要做好三件事:
数据分割:时间序列数据要特别注意避免未来信息泄露
# 按时间划分训练测试集 train = clean_data[clean_data['Order Date'] < '2023-06-01'] test = clean_data[clean_data['Order Date'] >= '2023-06-01']分类编码:对于树模型用OrdinalEncoder,线性模型用OneHotEncoder
from sklearn.preprocessing import OrdinalEncoder encoder = OrdinalEncoder() train[['Product Category']] = encoder.fit_transform(train[['Product Category']]) test[['Product Category']] = encoder.transform(test[['Product Category']])评估指标:根据业务目标选择合适指标
- 分类问题:AUC、F1、Precision/Recall
- 回归问题:MAE、RMSE、R²
- 排序问题:NDCG、MAP
最后用pd.get_dummies()处理分类变量,确保训练集和测试集特征维度一致:
# 确保测试集包含训练集所有类别 final_train = pd.get_dummies(train, columns=['Region']) final_test = pd.get_dummies(test, columns=['Region']) final_test = final_test.reindex(columns=final_train.columns, fill_value=0)记住:数据准备的质量决定模型效果的上限。花在数据上的时间,最终都会在模型效果上回报给你。