1. 数据清洗与变换的核心概念
数据清洗与变换是数据分析流程中最为关键的预处理环节。当原始数据存在缺失、异常、不一致等问题时,直接进行分析会导致结果偏差甚至完全错误。数据变换则是将清洗后的数据转换为更适合建模和分析的形式。
在实际项目中,数据清洗通常占据整个数据分析流程60%以上的时间。根据IBM的研究,数据科学家平均花费80%的时间在数据清洗和准备上。这充分说明了数据质量对分析结果的决定性影响。
常见的数据质量问题包括:缺失值(约占总数据量的5-15%)、异常值(3-8%)、不一致数据(如日期格式混乱)以及重复数据(电商数据中可达10%)。
2. 数据清洗的完整流程与技术要点
2.1 缺失值处理的三层策略
缺失值处理需要根据业务场景和数据特性选择适当的方法:
-
直接删除法(适合缺失率<5%的情况):
python复制df.dropna(subset=['重要字段'], inplace=True)- 优点:操作简单,不影响数据分布
- 缺点:损失样本信息
-
统计填充法(适合数值型变量):
python复制# 使用中位数填充(抗异常值) df['收入'].fillna(df['收入'].median(), inplace=True) # 使用KNN填充(考虑特征相关性) from sklearn.impute import KNNImputer imputer = KNNImputer(n_neighbors=5) df_filled = imputer.fit_transform(df[['年龄','收入','消费金额']]) -
模型预测法(适合高价值字段):
python复制from sklearn.ensemble import RandomForestRegressor # 将数据分为有缺失和无缺失两部分 known = df[df['目标字段'].notnull()] unknown = df[df['目标字段'].isnull()] # 训练预测模型 model = RandomForestRegressor() model.fit(known[['特征1','特征2']], known['目标字段']) # 预测缺失值 predicted = model.predict(unknown[['特征1','特征2']])
2.2 异常值检测的四种武器
-
3σ原则(适合正态分布数据):
python复制mean, std = df['金额'].mean(), df['金额'].std() lower, upper = mean - 3*std, mean + 3*std outliers = df[(df['金额'] < lower) | (df['金额'] > upper)] -
IQR方法(适合非正态分布):
python复制Q1 = df['年龄'].quantile(0.25) Q3 = df['年龄'].quantile(0.75) IQR = Q3 - Q1 outliers = df[(df['年龄'] < Q1-1.5*IQR) | (df['年龄'] > Q3+1.5*IQR)] -
孤立森林(适合高维数据):
python复制from sklearn.ensemble import IsolationForest clf = IsolationForest(contamination=0.05) preds = clf.fit_predict(df[['特征1','特征2']]) outliers = df[preds == -1] -
可视化检测(快速验证):
python复制import seaborn as sns sns.boxplot(x=df['交易金额'])
2.3 数据一致性处理的实战技巧
-
日期格式标准化:
python复制from datetime import datetime def parse_date(date_str): for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m.%d.%Y'): try: return datetime.strptime(date_str, fmt) except ValueError: continue return pd.NaT # 无法解析的返回缺失值 df['日期'] = df['日期'].apply(parse_date) -
分类数据编码:
python复制# 有序分类变量 size_map = {'XS':1, 'S':2, 'M':3, 'L':4, 'XL':5} df['尺寸编码'] = df['尺寸'].map(size_map) # 无序分类变量(避免虚拟变量陷阱) df = pd.get_dummies(df, columns=['颜色'], drop_first=True) -
单位统一处理:
python复制def standardize_weight(weight): if 'kg' in str(weight): return float(weight.replace('kg','')) elif 'g' in str(weight): return float(weight.replace('g',''))/1000 else: return float(weight) df['标准重量'] = df['重量'].apply(standardize_weight)
3. 数据变换的核心技术与Python实现
3.1 特征缩放的五种方法对比
| 方法 | 公式 | 适用场景 | Scikit-learn实现 |
|---|---|---|---|
| 标准化 | (x-μ)/σ | 数据服从正态分布 | StandardScaler |
| 最大最小缩放 | (x-min)/(max-min) | 数据有明确边界 | MinMaxScaler |
| 鲁棒缩放 | (x-Q1)/(Q3-Q1) | 数据含异常值 | RobustScaler |
| 对数变换 | log(1+x) | 右偏分布 | FunctionTransformer |
| 分位数变换 | Φ⁻¹(F(x)) | 任意分布转正态 | QuantileTransformer |
python复制from sklearn.preprocessing import QuantileTransformer
qt = Quantile[Transformer](https://taotoken.net?utm_source=general)(output_distribution='normal')
df['收入_normal'] = qt.fit_transform(df[['收入']])
3.2 基于分位数的稳健变换实现
分位数变换特别适合处理存在异常值的数据,以下是一个完整实现:
python复制import numpy as np
from scipy.stats import norm
def quantile_transform(series, n_quantiles=1000):
"""
手动实现分位数变换
参数:
series: pd.Series 待变换数据
n_quantiles: int 分位数数量
返回:
变换后的Series
"""
# 计算经验分位数
quantiles = np.linspace(0, 1, n_quantiles+1)
values = series.quantile(quantiles).values
# 创建映射函数
def _transform(x):
# 找到x所在的分位区间
idx = np.searchsorted(values, x)
idx = np.clip(idx, 1, len(values)-1)
# 线性插值
lower = values[idx-1]
upper = values[idx]
alpha = (x - lower) / (upper - lower) if upper != lower else 0.5
mapped = quantiles[idx-1] + alpha*(quantiles[idx]-quantiles[idx-1])
# 映射到标准正态分布
return norm.ppf(mapped)
return series.apply(_transform)
# 使用示例
df['金额_robust'] = quantile_transform(df['交易金额'])
3.3 非线性特征创造的实战案例
-
多项式特征(发现变量间交互作用):
python复制from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=2, interaction_only=True) interactions = poly.fit_transform(df[['年龄', '收入']]) -
分箱处理(将连续变量离散化):
python复制# 等宽分箱 df['年龄_bin'] = pd.cut(df['年龄'], bins=5, labels=False) # 等频分箱(更推荐) df['收入_bin'] = pd.qcut(df['收入'], q=10, labels=False, duplicates='drop') -
时间特征提取:
python复制df['交易小时'] = df['交易时间'].dt.hour df['是否周末'] = df['交易时间'].dt.weekday >= 5 df['季度'] = df['交易时间'].dt.quarter
4. 高级数据变换技术
4.1 文本数据的向量化方法
-
TF-IDF向量化:
python复制from sklearn.feature_extraction.text import TfidfVectorizer tfidf = TfidfVectorizer(max_features=500, stop_words='english') tfidf_matrix = tfidf.fit_transform(df['产品描述']) -
词嵌入(Word2Vec):
python复制from gensim.models import Word2Vec sentences = [text.split() for text in df['评论']] model = Word2Vec(sentences, vector_size=100, window=5, min_count=3) # 获取文档向量(词向量的平均) def get_doc_vector(words): vectors = [model.wv[word] for word in words if word in model.wv] return np.mean(vectors, axis=0) if vectors else np.zeros(100) df['评论向量'] = df['评论'].apply(lambda x: get_doc_vector(x.split()))
4.2 图像数据的预处理流程
python复制from PIL import Image
import numpy as np
def preprocess_image(image_path, target_size=(224,224)):
# 读取图像
img = Image.open(image_path)
# 调整尺寸
img = img.resize(target_size)
# 转换为数组并归一化
img_array = np.array(img) / 255.0
# 通道处理(RGB转BGR)
if img_array.shape[-1] == 3:
img_array = img_array[..., ::-1]
# 标准化(使用ImageNet均值标准差)
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
img_array = (img_array - mean) / std
return img_array
4.3 时间序列的特征工程
-
统计特征提取:
python复制def extract_ts_features(series, window=30): return { 'mean': series.mean(), 'std': series.std(), 'max': series.max(), 'min': series.min(), 'q25': series.quantile(0.25), 'q75': series.quantile(0.75), 'skew': series.skew(), 'kurt': series.kurt(), 'autocorr': series.autocorr(), 'rolling_mean': series.rolling(window).mean()[-1] } features = df.groupby('用户ID')['消费金额'].apply(extract_ts_features) -
傅里叶变换:
python复制from scipy.fft import fft def get_fft_features(series, n_components=5): fft_result = fft(series.values) magnitudes = np.abs(fft_result) phases = np.angle(fft_result) return { f'fft_mag_{i}': magnitudes[i] for i in range(n_components) }
5. 数据清洗与变换的常见陷阱与解决方案
5.1 数据泄露的预防措施
-
时间序列数据:确保只用历史数据计算统计量
python复制from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) for train_idx, test_idx in tscv.split(X): # 只在训练集上计算统计量 scaler.fit(X.iloc[train_idx]) X_train = scaler.transform(X.iloc[train_idx]) X_test = scaler.transform(X.iloc[test_idx]) -
分组数据处理:避免组间信息泄露
python复制from sklearn.model_selection import GroupKFold gkf = GroupKFold(n_splits=5) for train_idx, test_idx in gkf.split(X, groups=df['用户ID']): # 确保同一用户不会同时出现在训练集和测试集
5.2 类别不平衡的处理策略
-
过采样技术(SMOTE):
python复制from imblearn.over_sampling import SMOTE smote = SMOTE(sampling_strategy='minority') X_res, y_res = smote.fit_resample(X, y) -
代价敏感学习:
python复制from sklearn.linear_model import LogisticRegression # 根据类别比例自动调整class_weight model = LogisticRegression(class_weight='balanced') -
评估指标选择:
python复制from sklearn.metrics import classification_report # 关注召回率和F1分数而非准确率 print(classification_report(y_test, y_pred))
5.3 大数据集的优化处理
-
分块处理技术:
python复制chunk_size = 100000 for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size): process_chunk(chunk) -
内存优化技巧:
python复制# 降低数值类型精度 df['金额'] = df['金额'].astype('float32') # 使用分类类型 df['城市'] = df['城市'].astype('category') -
并行处理:
python复制from joblib import Parallel, delayed def process_column(col): return col.fillna(col.median()) results = Parallel(n_jobs=4)( delayed(process_column)(df[col]) for col in df.columns ) df_processed = pd.concat(results, axis=1)
6. 完整的数据清洗与变换Pipeline实现
以下是一个整合了上述技术的完整Pipeline示例:
python复制from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import (StandardScaler, OneHotEncoder,
FunctionTransformer)
from sklearn.feature_extraction.text import TfidfVectorizer
# 数值型特征处理
numeric_[transformer](https://taotoken.net/?utm_source=general) = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# 分类特征处理
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
# 文本特征处理
text_transformer = Pipeline(steps=[
('tfidf', TfidfVectorizer(max_features=100))
])
# 分位数变换
def quantile_transform(X):
return np.log1p(X)
quantile_transformer = FunctionTransformer(quantile_transform)
# 组合所有转换器
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, ['年龄', '收入']),
('cat', categorical_transformer, ['性别', '城市']),
('text', text_transformer, '产品评价'),
('quant', quantile_transformer, ['交易金额'])
])
# 完整Pipeline
pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier())
])
# 使用示例
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
在实际项目中,我通常会根据数据特性调整这个基础Pipeline。例如对于时间序列数据会增加滞后特征,对于空间数据会添加地理编码转换。关键是要建立可复用的转换模块,方便在不同项目中快速部署。
