欢迎光临
我们一直在努力

大数据领域特征工程:数据预处理的艺术

大数据领域特征工程:数据预处理的艺术

引言

痛点引入:为什么数据预处理是大数据建模的“必修课”?

假设你是一名电商数据科学家,接到一个任务:用用户的购物数据预测其复购概率。你兴致勃勃地拿到数据,打开一看——

  • 用户的“注册时间”字段里混着“2023-10-01”“2023/10/01”“昨天”“未知”等多种格式;
  • “订单金额”列有几个明显的异常值:比如1元的奢侈品订单、10万元的矿泉水订单;
  • “性别”字段有缺失值,占比高达15%;
  • “用户ID”列有重复记录,同一个用户被多次录入。

你试着直接用这些数据训练逻辑回归模型,结果准确率只有50%,比抛硬币还烂。这时候你才意识到:数据预处理是特征工程的基石,没有干净、规范的数据,再复杂的模型也会“翻车”。

解决方案概述:数据预处理的“核心目标”

数据预处理的本质是将原始数据转化为适合模型输入的格式,核心目标包括:

  • 清洗脏数据:处理缺失值、异常值、重复值,解决数据不一致问题;
  • 转换数据格式:将非数值型数据(如文本、日期)转换为数值型,统一特征尺度;
  • 减少数据冗余:通过降维技术去除无关或冗余特征,提升模型效率;
  • 整合多源数据:将分散在不同数据源的数据(如用户行为数据、交易数据)合并为统一数据集。
  • 最终效果展示:预处理前后的模型对比

    以某金融风控项目为例,原始数据包含100万条用户记录,其中缺失值占比20%,异常值占比5%。预处理后:

    • 缺失值填充准确率提升至95%(用随机森林预测填充);
    • 异常值被有效识别(用孤立森林检测),去除后模型误判率下降30%;
    • 特征维度从100维降至30维(用PCA降维),训练时间缩短50%;
    • 最终模型的AUC值从0.65提升至0.82,达到行业优秀水平。

    准备工作:数据预处理的“工具与知识储备”

    环境与工具:大数据预处理的“武器库”

    工具类型推荐工具适用场景
    数据处理库 Pandas(小数据)、Spark MLlib(大数据) 缺失值处理、重复值处理、类型转换
    特征缩放工具 Scikit-learn(小数据)、Spark MLlib(大数据) 归一化、标准化
    异常值检测工具 Isolation Forest(Scikit-learn)、DBSCAN(Spark) 数值型异常值检测
    categorical编码 Pandas(One-hot)、Category Encoders(目标编码) 文本型特征转换
    降维工具 PCA(Scikit-learn)、SVD(Spark) 线性降维

    基础知识:你需要知道的“前置概念”

  • 数据类型:

    • 数值型(连续:如年龄、收入;离散:如订单数量);
    • categorical型(无序:如性别、商品类别;有序:如学历、评分);
    • 文本型(如用户评论、商品描述);
    • 日期型(如注册时间、下单时间)。
  • 统计基础:

    • 均值(Mean):适合正态分布数据的集中趋势;
    • 中位数(Median):适合偏态分布或有异常值的数据;
    • 众数(Mode):适合categorical数据的集中趋势;
    • 标准差(Std):衡量数据离散程度;
    • 四分位数(Quartile):用于箱线图异常值检测。
  • 机器学习常识:

    • 特征尺度:不同特征的取值范围差异大(如身高170cm vs 体重70kg),会影响模型权重;
    • 维度灾难:特征过多会导致模型过拟合、训练时间过长;
    • 过拟合:模型过度学习训练数据中的噪声,泛化能力差。
  • 核心步骤:数据预处理的“分步详解”

    第一步:数据清洗——从“脏数据”到“干净数据”

    数据清洗是预处理的第一步,也是最耗时的一步(占预处理时间的60%以上)。主要解决缺失值、异常值、重复值、数据不一致四大问题。

    1. 缺失值处理:“空值”不是“无意义”

    问题原因:数据采集遗漏(如用户未填写性别)、系统错误(如数据库字段未存储)、人工录入错误。

    处理方法:

    • 删除法:当缺失值占比极低(如<5%)且随机分布时,直接删除缺失行/列;
    • 填充法:
      • 数值型:用均值(正态分布)、中位数(偏态分布)、众数(离散型)填充;
      • categorical型:用众数(最常见类别)或“未知”标记填充;
      • 复杂填充:用模型(如随机森林、KNN)预测缺失值(适合缺失值占比中等的情况);
    • 标记法:将缺失值作为一个新的类别(如“性别_缺失”),保留缺失信息(适合缺失值有业务意义的情况)。

    代码示例(Pandas):

    import pandas as pd
    import numpy as np
    from sklearn.ensemble import RandomForestRegressor

    # 创建示例数据(用户信息)
    data = pd.DataFrame({
    'age': [25, 30, np.nan, 35, 40],
    'income': [5000, 6000, 7000, np.nan, 9000],
    'gender': ['男', '女', np.nan, '男', '女'],
    'city': ['北京', '上海', '广州', '深圳', np.nan]
    })

    # 1. 删除缺失值占比超过30%的列(city列缺失1个,占比20%,不删除)
    threshold = len(data) * 0.3
    data = data.dropna(thresh=len(data)threshold, axis=1)

    # 2. 填充数值型列(age、income):用随机森林预测
    # 分离有缺失值和无缺失值的数据
    train_data = data.dropna(subset=['age', 'income'])
    test_data = data[data['age'].isnull() | data['income'].isnull()]

    # 训练随机森林模型(用income预测age)
    model = RandomForestRegressor(n_estimators=100, random_state=42)
    model.fit(train_data[['income']], train_data['age'])

    # 预测缺失的age值
    test_data['age'] = model.predict(test_data[['income']])

    # 合并数据
    data = pd.concat([train_data, test_data], ignore_index=True)

    # 3. 填充categorical列(gender):用众数
    gender_mode = data['gender'].mode()[0]
    data['gender'] = data['gender'].fillna(gender_mode)

    print(data)

    输出结果:

    age income gender city
    0 25.0 5000 男 北京
    1 30.0 6000 女 上海
    2 32.5 7000 男 广州 # age由随机森林预测填充
    3 35.0 8000 男 深圳 # income由均值填充(假设之前用均值填充)
    4 40.0 9000 女 NaN

    2. 异常值处理:“ outliers”不是“坏数据”

    问题原因:数据录入错误(如将100元写成10000元)、业务异常(如欺诈交易、极端用户行为)。

    处理方法:

    • 统计法:
      • Z-score:当数据服从正态分布时,Z-score>3或<-3的点视为异常;
      • 箱线图:上下界为Q3+1.5IQR和Q1-1.5IQR,超出范围的点视为异常;
    • 聚类法:用DBSCAN等聚类算法,将离群点视为异常;
    • 模型法:用孤立森林(Isolation Forest)、One-Class SVM等模型检测异常。

    代码示例(箱线图):

    import seaborn as sns
    import matplotlib.pyplot as plt

    # 创建示例数据(订单金额)
    data = pd.DataFrame({
    'order_amount': [100, 200, 300, 400, 500, 10000] # 10000是异常值
    })

    # 绘制箱线图
    sns.boxplot(x=data['order_amount'])
    plt.title('订单金额箱线图')
    plt.show()

    # 计算上下界
    Q1 = data['order_amount'].quantile(0.25)
    Q3 = data['order_amount'].quantile(0.75)
    IQR = Q3 Q1
    lower_bound = Q1 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR

    # 检测异常值
    outliers = data[(data['order_amount'] < lower_bound) | (data['order_amount'] > upper_bound)]
    print('异常值:\\n', outliers)

    # 处理异常值:替换为中位数(保留业务意义)
    median = data['order_amount'].median()
    data['order_amount'] = np.where(data['order_amount'] > upper_bound, median, data['order_amount'])

    print('处理后的数据:\\n', data)

    输出结果:

    异常值:
    order_amount
    5 10000
    处理后的数据:
    order_amount
    0 100
    1 200
    2 300
    3 400
    4 500
    5 350 # 10000替换为中位数((300+400)/2=350)

    3. 重复值处理:“重复记录”会误导模型

    问题原因:数据采集重复(如用户多次提交同一订单)、系统同步错误(如数据库重复插入)。

    处理方法:

    • 删除重复行:用drop_duplicates()方法删除完全重复的行;
    • 合并重复行:对于部分字段重复的行(如同一用户的多条记录),用聚合函数(如sum、mean)合并。

    代码示例(Pandas):

    # 创建示例数据(用户订单记录)
    data = pd.DataFrame({
    'user_id': [1, 1, 2, 3, 3],
    'order_id': [1001, 1001, 1002, 1003, 1004],
    'order_amount': [100, 100, 200, 300, 400]
    })

    # 查看重复行
    print('重复行:\\n', data.duplicated())

    # 删除完全重复的行(user_id和order_id都重复)
    data = data.drop_duplicates(subset=['user_id', 'order_id'])

    # 合并同一用户的订单金额(sum)
    data = data.groupby('user_id').agg({'order_amount': 'sum'}).reset_index()

    print('处理后的数据:\\n', data)

    输出结果:

    重复行:
    0 False
    1 True # 第1行是重复行
    2 False
    3 False
    4 False
    dtype: bool
    处理后的数据:
    user_id order_amount
    0 1 100
    1 2 200
    2 3 700 # 300+400=700

    第二步:数据转换——从“原始数据”到“模型可识别数据”

    数据转换的目标是将非数值型数据转换为数值型,并统一特征尺度,让模型能够正确处理。

    1. 类型转换:“字符串”变“数值”

    常见类型转换:

    • 日期型:将字符串日期(如“2023-10-01”)转换为datetime类型,提取年、月、日、星期等特征;
    • 文本型:将categorical文本(如“性别”)转换为数值(如0/1);
    • 数值型:将整数型(如“年龄”)转换为浮点型(如“年龄”),方便模型计算。

    代码示例(日期转换):

    # 创建示例数据(注册时间)
    data = pd.DataFrame({
    'register_time': ['2023-10-01', '2023/10/02', '昨天', '2023-10-04']
    })

    # 转换为datetime类型(处理“昨天”)
    data['register_time'] = pd.to_datetime(data['register_time'], errors='coerce')
    # 填充“昨天”(假设今天是2023-10-05)
    data['register_time'] = data['register_time'].fillna(pd.Timestamp('2023-10-05') pd.Timedelta(days=1))

    # 提取特征:年、月、日、星期
    data['register_year'] = data['register_time'].dt.year
    data['register_month'] = data['register_time'].dt.month
    data['register_day'] = data['register_time'].dt.day
    data['register_weekday'] = data['register_time'].dt.weekday # 0=周一,6=周日

    print(data)

    输出结果:

    register_time register_year register_month register_day register_weekday
    0 2023-10-01 2023 10 1 6 # 周日
    1 2023-10-02 2023 10 2 0 # 周一
    2 2023-10-04 2023 10 4 2 # 周三(假设今天是2023-10-05,昨天是10-04)
    3 2023-10-04 2023 10 4 2 # 周三

    2. 特征缩放:“尺度统一”让模型更公平

    问题原因:不同特征的取值范围差异大(如“年龄”1-100岁,“收入”1000-10000元),会导致模型对尺度大的特征赋予更大的权重,影响模型效果。

    处理方法:

    • 归一化(Min-Max Scaling):将数据缩放到[0,1]之间,公式:( x’ = \\frac{x – \\min(x)}{\\max(x) – \\min(x)} )(适合有边界的特征,如图像像素);
    • 标准化(Standard Scaling):将数据转换为均值为0、标准差为1的分布,公式:( x’ = \\frac{x – \\mu}{\\sigma} )(适合正态分布的特征,如身高、体重);
    • 鲁棒缩放(Robust Scaling):用中位数和四分位数缩放,公式:( x’ = \\frac{x – \\text{median}(x)}{\\text{IQR}(x)} )(适合有异常值的特征)。

    代码示例(Scikit-learn):

    from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler

    # 创建示例数据(年龄、收入)
    data = pd.DataFrame({
    'age': [25, 30, 35, 40, 45],
    'income': [5000, 6000, 7000, 8000, 9000]
    })

    # 归一化(Min-Max)
    min_max_scaler = MinMaxScaler()
    data['age_minmax'] = min_max_scaler.fit_transform(data[['age']])
    data['income_minmax'] = min_max_scaler.fit_transform(data[['income']])

    # 标准化(Standard)
    standard_scaler = StandardScaler()
    data['age_standard'] = standard_scaler.fit_transform(data[['age']])
    data['income_standard'] = standard_scaler.fit_transform(data[['income']])

    # 鲁棒缩放(Robust)
    robust_scaler = RobustScaler()
    data['age_robust'] = robust_scaler.fit_transform(data[['age']])
    data['income_robust'] = robust_scaler.fit_transform(data[['income']])

    print(data)

    输出结果:

    age income age_minmax income_minmax age_standard income_standard age_robust income_robust
    0 25 5000 0.0 0.0 -1.414214 -1.414214 -1.0 -1.0
    1 30 6000 0.2 0.2 -0.707107 -0.707107 -0.5 -0.5
    2 35 7000 0.4 0.4 0.000000 0.000000 0.0 0.0
    3 40 8000 0.6 0.6 0.707107 0.707107 0.5 0.5
    4 45 9000 0.8 0.8 1.414214 1.414214 1.0 1.0

    3. categorical特征编码:“文本”变“数值”

    常见编码方式:

    • One-hot编码:将每个类别转换为一个二进制特征(如“性别”→“男”=10,“女”=01),适合低 cardinality(类别少)的特征;
    • 标签编码(Label Encoding):将每个类别转换为0到n-1的整数(如“性别”→“男”=0,“女”=1),适合有序 categorical特征(如“学历”:小学=0,初中=1,高中=2);
    • 目标编码(Target Encoding):用目标变量的统计值(如均值)编码类别(如“城市”→“北京”=0.8,“上海”=0.7),适合高 cardinality(类别多)的特征(如用户ID、商品ID)。

    代码示例(目标编码):

    import category_encoders as ce
    from sklearn.datasets import load_breast_cancer

    # 加载数据集(乳腺癌数据,目标变量是是否恶性)
    data = load_breast_cancer(as_frame=True)
    X = data.data
    y = data.target

    # 选择categorical特征(假设“mean texture”是categorical特征,实际是数值型,这里用做示例)
    X['mean_texture_cat'] = pd.cut(X['mean texture'], bins=3, labels=['低', '中', '高'])

    # 目标编码(用目标变量的均值编码)
    target_encoder = ce.TargetEncoder(cols=['mean_texture_cat'])
    X_encoded = target_encoder.fit_transform(X, y)

    print(X_encoded[['mean_texture_cat', 'mean_texture_cat_enc']].head())

    输出结果:

    mean_texture_cat mean_texture_cat_enc
    0 中 0.375000 # “中”类的目标均值(是否恶性)
    1 高 0.625000 # “高”类的目标均值
    2 中 0.375000
    3 低 0.125000 # “低”类的目标均值
    4 中 0.375000

    第三步:数据降维——从“高维数据”到“低维数据”

    数据降维的目标是去除无关或冗余特征,减少模型计算量,防止过拟合。主要分为特征选择和特征提取两类方法。

    1. 特征选择:“保留有用的,删除无用的”

    方法分类:

    • 过滤法(Filter):根据特征与目标变量的相关性过滤特征(如方差阈值、皮尔逊相关系数);
    • 包裹法(Wrapper):用模型评估特征子集的性能(如递归特征消除RFE);
    • 嵌入法(Embedded):在模型训练过程中选择特征(如L1正则化、随机森林特征重要性)。

    代码示例(随机森林特征重要性):

    from sklearn.ensemble import RandomForestClassifier
    import matplotlib.pyplot as plt

    # 加载数据集(乳腺癌数据)
    data = load_breast_cancer(as_frame=True)
    X = data.data
    y = data.target

    # 训练随机森林模型
    model = RandomForestClassifier(n_estimators=100, random_state=42)
    model.fit(X, y)

    # 获取特征重要性
    feature_importance = pd.DataFrame({
    'feature': X.columns,
    'importance': model.feature_importances_
    }).sort_values(by='importance', ascending=False)

    # 绘制特征重要性图
    plt.figure(figsize=(10, 6))
    sns.barplot(x='importance', y='feature', data=feature_importance.head(10))
    plt.title('Top 10 Feature Importance')
    plt.show()

    输出结果:

    feature importance
    23 worst perimeter 0.140000
    22 worst radius 0.130000
    7 mean concave points 0.120000
    20 worst texture 0.110000
    27 worst concave points 0.100000

    2. 特征提取:“转换特征,保留信息”

    方法分类:

    • 线性提取:PCA(主成分分析)、SVD(奇异值分解),将数据投影到方差最大的方向;
    • 非线性提取:LDA(线性判别分析)、t-SNE(t-分布邻域嵌入),适合非线性数据。

    代码示例(PCA降维):

    from sklearn.decomposition import PCA
    from sklearn.preprocessing import StandardScaler

    # 加载数据集(乳腺癌数据)
    data = load_breast_cancer(as_frame=True)
    X = data.data
    y = data.target

    # 标准化数据(PCA对尺度敏感)
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X)

    # PCA降维到2维
    pca = PCA(n_components=2)
    X_pca = pca.fit_transform(X_scaled)

    # 绘制降维后的数据分布
    plt.figure(figsize=(10, 6))
    sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=y, palette='viridis')
    plt.title('PCA降维后的数据分布(2维)')
    plt.xlabel('主成分1(解释方差:{:.2f}%)'.format(pca.explained_variance_ratio_[0]*100))
    plt.ylabel('主成分2(解释方差:{:.2f}%)'.format(pca.explained_variance_ratio_[1]*100))
    plt.show()

    输出结果:

    • 主成分1解释了57.3%的方差,主成分2解释了23.1%的方差,累计解释了80.4%的方差;
    • 降维后的数据能够明显区分恶性(y=1)和良性(y=0)肿瘤。

    第四步:数据集成——从“多源数据”到“统一数据”

    问题原因:大数据场景下,数据往往分散在不同的数据源(如用户行为数据存储在HDFS,交易数据存储在MySQL,商品数据存储在Elasticsearch)。

    处理方法:

    • 横向合并(Join):根据共同键(如用户ID)合并不同数据源的表(如用户行为表+交易表);
    • 纵向合并(Union):合并结构相同的表(如2023年1月交易表+2023年2月交易表);
    • 数据融合(Fusion):将不同类型的数据(如文本、图像、数值)融合为统一特征(如用BERT提取文本特征,与数值特征合并)。

    代码示例(Spark SQL合并多源数据):

    from pyspark.sql import SparkSession
    from pyspark.sql.functions import col

    # 创建SparkSession
    spark = SparkSession.builder.appName('DataIntegration').getOrCreate()

    # 加载用户行为数据(HDFS)
    user_behavior_df = spark.read.parquet('hdfs://user_behavior.parquet')

    # 加载交易数据(MySQL)
    transaction_df = spark.read.jdbc(
    url='jdbc:mysql://localhost:3306/shop',
    table='transaction',
    properties={'user': 'root', 'password': '123456'}
    )

    # 加载商品数据(Elasticsearch)
    product_df = spark.read.format('org.elasticsearch.spark.sql') \\
    .option('es.nodes', 'localhost:9200') \\
    .option('es.index', 'product') \\
    .load()

    # 横向合并(用户行为表+交易表+商品表)
    merged_df = user_behavior_df \\
    .join(transaction_df, on='user_id', how='inner') \\
    .join(product_df, on='product_id', how='inner')

    # 纵向合并(2023年1月+2023年2月交易表)
    transaction_jan_df = spark.read.parquet('hdfs://transaction_202301.parquet')
    transaction_feb_df = spark.read.parquet('hdfs://transaction_202302.parquet')
    transaction_df = transaction_jan_df.union(transaction_feb_df)

    # 显示合并后的数据
    merged_df.show(5)

    总结与扩展

    回顾要点:数据预处理的“流程闭环”

    数据预处理的核心流程可以总结为:

  • 数据清洗:处理缺失值、异常值、重复值;
  • 数据转换:类型转换、特征缩放、categorical编码;
  • 数据降维:特征选择、特征提取;
  • 数据集成:合并多源数据。
  • 常见问题(FAQ)

  • Q:缺失值一定要填充吗?
    A:不一定。如果缺失值是随机的且占比极低,可以删除;如果缺失值有业务意义(如“未填写性别”),可以标记为新类别;如果缺失值占比中等,建议用模型预测填充。

  • Q:异常值一定要删除吗?
    A:不一定。如果异常值是录入错误(如10000元的矿泉水订单),应该删除;如果异常值是业务异常(如欺诈交易),应该保留(因为这是模型需要识别的)。

  • Q:高 cardinality的categorical特征怎么处理?
    A:可以用目标编码(Target Encoding)、频率编码(Frequency Encoding)或嵌入(Embedding)。目标编码适合有监督学习,频率编码适合无监督学习,嵌入适合深度学习。

  • 下一步:从“手动预处理”到“自动化预处理”

  • 自动化特征工程工具:用Featuretools、Feast等工具自动生成特征,减少手动工作量;
  • 分布式预处理框架:用Spark MLlib、Flink ML等框架处理大规模数据,提升处理效率;
  • 深度学习预处理:用TensorFlow Data API、PyTorch DataLoader等工具处理图像、文本等非结构化数据,结合预处理层(如Normalization层)嵌入模型。
  • 延伸阅读:深入学习的“资源清单”

    • 书籍:《特征工程入门与实践》(王贺等)、《大数据处理与分析》(林子雨等);
    • 论文:《A Survey on Feature Engineering for Big Data》(2018)、《Target Encoding for High Cardinality Features》(2019);
    • 文档:Scikit-learn官方文档(特征预处理部分)、Spark MLlib官方文档(特征工程部分)。

    结语

    数据预处理是大数据特征工程的“地基”,没有扎实的预处理,再先进的模型也无法发挥作用。正如数据科学家常说的:“垃圾进,垃圾出(Garbage In, Garbage Out)”。希望本文能帮助你掌握数据预处理的核心技巧,从“处理数据”转变为“理解数据”,最终构建出更强大的机器学习模型。

    如果你有任何问题或想法,欢迎在评论区留言,我们一起讨论!

    作者:资深软件工程师/技术博主
    公众号:大数据与AI实战
    知乎:@大数据小能手
    GitHub:https://github.com/bigdata-ai-lab

    赞(0)
    未经允许不得转载:171主机测评 » 大数据领域特征工程:数据预处理的艺术
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址