机器学习从入门到精通 - 数据预处理实战秘籍:清洗、转换与特征工程入门

机器学习从入门到精通 - 数据预处理实战秘籍:清洗、转换与特征工程入门

开场:别急着喂数据,先看看这碗"米"好不好!

老话说"巧妇难为无米之炊",搞机器学习的朋友们,咱们手里这堆数据就是"米"。但你想过没有?直接从田里收上来的稻谷,能直接下锅煮饭吗?得晒干、脱壳、筛杂质... 预处理就是磨这把米!很多新手吭哧吭哧调模型,效果却惨不忍睹,八成是"米"没淘干净。这篇长文,我就跟你掏心窝子聊聊数据预处理里的那些门道、暗坑和救命技巧。从怎么洗掉脏数据、怎么给数据"变形",再到怎么"无中生有"造出好特征,咱们一步一个坑(不是,一步一个脚印)地走通它。读完这篇,我保证你再看原始数据,眼神都不一样了------那都是闪闪发光的金矿胚子啊!

第一章:数据清洗 - 给数据搓个澡,别让"泥点子"毁了模型

为啥要洗?脏数据有多可怕?

想象一下,你训练一个预测房价的模型。数据里有个豪宅面积写着"五百平米",另一个老破小面积写成了"500"。模型一看,老破小面积是500,豪宅是"五百",它很可能觉得"五百"是某种神秘代码或者干脆忽略掉------结果就是学习偏差。更别提那些缺失值、异常值了... 这些脏东西不清理,模型学到的就是失真的世界。

实战第一坑:缺失值处理 - 删?补?这是个哲学问题!

新手最容易掉进去的坑就是无脑删!看见空值(NaN, Null)就 df.dropna() 梭哈一把删。等等,这里有个矛盾点!数据量本身就少得可怜,你再删掉几行关键样本,模型直接饿死了怎么办?

为什么不能乱删?

  • 代价高昂:特别是某些稀有事件(比如欺诈交易)的数据,删一个少一个。
  • 引入偏差:如果缺失不是随机的(比如:收入高的人更不愿意填写收入栏),删除后样本就偏离了真实分布。

更聪明的"补"法:

  1. 统计量填充(适合数值型): 用均值、中位数、众数。为啥用中位数?因为它对异常值不敏感!比如小区房价,一个天价别墅会拉高均值,中位数就更稳健。

    python 复制代码
    # 计算中位数填充 'price' 列的缺失值
    median_price = df['price'].median()
    df['price'].fillna(median_price, inplace=True)  # 新手常犯的错:忘记 inplace=True 或者忘记赋值回去!
  2. 模型预测填充(更高级): 用其他没缺失的特征,训练一个回归/分类模型来预测缺失值。这招复杂点,但更精准。

    python 复制代码
    from sklearn.experimental import enable_iterative_imputer
    from sklearn.impute import IterativeImputer
    from sklearn.ensemble import RandomForestRegressor
    
    # 使用随机森林预测缺失的 'age'
    imputer = IterativeImputer(estimator=RandomForestRegressor(), max_iter=10, random_state=42)
    df[['age']] = imputer.fit_transform(df[['age']])  # 注意:确保输入是二维数组 [[]]
  3. 特殊值标记(分类/时序常用): 对于实在不好补的,干脆加一列标志 is_missing 告诉模型"这个值缺了",让模型自己判断缺了代表啥意思。

流程图:缺失值处理决策树
Yes, 很高 >30% No 随机 非随机 列缺失多 行缺失多且样本珍贵 发现缺失值 缺失比例高吗? 考虑删除整个特征或行? 慎重! 缺失有模式吗 随机缺失? 统计量填充 均值/中位数/众数 模型预测填充 或 特殊值标记 删除行还是列? 删特征 尝试填充或标记

实战第二坑:异常值 - 是金子还是老鼠屎?

数据里混进个身高3米8的大哥,或者年龄200岁的老寿星... 这些是宝贵的特殊样本,还是该清理的噪声?

为什么处理异常值?

异常值会像磁铁一样,把回归模型的线"吸"偏;也会让基于距离的算法(如KNN)直接懵圈;更会让需要计算均值和方差的算法(如PCA、标准化)结果失真。

怎么揪出它们?

  1. 可视化大法好! 箱线图(Boxplot)、散点图(Scatter Plot)一眼看出离群点。

    python 复制代码
    import matplotlib.pyplot as plt
    import seaborn as sns
    
    # 绘制 'income' 的箱线图
    plt.figure(figsize=(10, 6))
    sns.boxplot(y=df['income'])
    plt.title('收入分布箱线图 - 揪出异常值')
    plt.show()  # 看到图上方的"小星星"了吗?就是它们!
  2. 统计方法:

    • Z-Score(标准分数): 假设数据近似正态分布。计算每个数据点的Z分数 (x - mean) / std。通常 |Z| > 3 的点就很可疑了。注意:异常值本身会影响mean和std! 这个标准化处理吧------其实对树模型影响有限。

      python 复制代码
      from scipy import stats
      z_scores = stats.zscore(df['income'])
      outliers = df[np.abs(z_scores) > 3]  # 找出Z分数绝对值大于3的异常点
    • IQR(四分位距): 更稳健,不受极端值影响。IQR = Q3 - Q1 (Q3是75%分位数,Q1是25%分位数)。通常认为小于 Q1 - 1.5 * IQR 或大于 Q3 + 1.5 * IQR 的是温和异常值(Mild Outlier),超过 Q1 - 3 * IQRQ3 + 3 * IQR 的是极端异常值。

      python 复制代码
      Q1 = df['income'].quantile(0.25)
      Q3 = df['income'].quantile(0.75)
      IQR = Q3 - Q1
      lower_bound = Q1 - 1.5 * IQR
      upper_bound = Q3 + 1.5 * IQR
      outliers = df[(df['income'] < lower_bound) | (df['income'] > upper_bound)]

怎么处理?坑来了!

  • 直接删除: 最简单粗暴,但要确认它真的是错误/无关噪音! 万一是特殊模式(比如超高消费的VIP用户)呢?删了就亏大了。

  • 盖帽法(Capping/Winsorizing): 温和异常值用边界值替换。比如把超过 upper_bound 的值都设为 upper_bound。这招能保留样本数量。

    python 复制代码
    df['income_capped'] = df['income'].clip(lower=lower_bound,
    upper=upper_bound)  # Pandas 的 clip 函数超方便
  • 分箱(Binning): 把连续值分成几个区间(如"低","中","高"),异常值会被归到最高/最低的箱子里稀释影响。

  • 变量转换: 取对数(np.log1p)、开方等压缩数值范围,也能减弱大值影响。特别适合右偏分布的收入、房价等数据。对了,我强烈推荐在对数转换前加个1,避免对0取对数出错 log(1+x)

第二章:数据转换 - 给数据"整容",让模型看得顺眼

为啥要转换?模型都是"颜控"!

不同的模型对数据的"长相"要求不同:

  • 基于距离/梯度的模型(KNN, K-Means, 线性回归, SVM, 神经网络): 要求特征尺度统一!不然"身高"单位是米(数值小),"收入"单位是元(数值大),算距离时收入就完全主导了结果,身高被忽略。
  • 树模型(决策树, 随机森林, XGBoost): 天生抗量纲,对单调变换(只改变大小顺序不改变相对关系)不敏感。所以标准化对它们效果有限。但! 特征缩放有时也能加速收敛(比如XGBoost的近似分桶算法)。
  • 带正则化的模型(Lasso, Ridge): 要求特征尺度相近,否则惩罚项对不同特征的影响不均。
核心转换术1:特征缩放(Feature Scaling)
  1. 标准化(Standardization / Z-Score Normalization)

    • 为什么用? 将数据变换为均值为0,标准差为1的正态分布(或接近)。公式直观:
      z = (x - μ) / σ

      • x:原始数据点
      • μ:特征列的均值
      • σ:特征列的标准差
    • 推导(其实很简单): 中心化 (x - μ) 让均值变0,除以标准差 σ 让数据的离散度统一。结果就是数据在新的坐标系下,中心是0,单位是"1个标准差"。

    • 代码: sklearnStandardScaler 是神器。

      python 复制代码
      from sklearn.preprocessing import StandardScaler
      
      scaler = StandardScaler()
      # 假设 df_num 是包含数值型特征的DataFrame
      scaled_features = scaler.fit_transform(df_num)  # 注意:这会返回 numpy array
      df_scaled = pd.DataFrame(scaled_features, columns=df_num.columns)
    • 适用场景: 最常用! 适用于数据大致符合正态分布,或分布未知但需要计算协方差(如PCA)的情况。

  2. 归一化(Min-Max Scaling / Normalization)

    • 为什么用? 将数据压缩到[0, 1]或[-1, 1]的固定区间。公式:
      x_scaled = (x - min) / (max - min)

      • min:特征列的最小值
      • max:特征列的最大值
    • 推导: 分子 (x - min) 将最小值拉到0点,分母 (max-min) 是原数据的全距。相除后,所有值被线性映射到[0,1]。

    • 代码: sklearnMinMaxScaler

      python 复制代码
      from sklearn.preprocessing import MinMaxScaler
      
      minmax_scaler = MinMaxScaler(feature_range=(0, 1))  # 默认[0,1], 可设[-1,1]
      minmax_features = minmax_scaler.fit_transform(df_num)
      df_minmax = pd.DataFrame(minmax_features, columns=df_num.columns)
    • 适用场景: 对输出范围有要求(如神经网络的激活函数输出常为[0,1]或[-1,1]),或者数据边界相对明确(如图像像素值0-255)。

标准化 vs 归一化 选哪个?

  • 数据有明显边界 (如像素值、百分比分数)→ 归一化
  • 数据有极端值分布未知/非均匀标准化(对异常值没那么敏感)。
  • 大多数情况 → 标准化是更安全、更通用的选择。我个人的偏好也是标准化为主。
核心转换术2:编码(Encoding)- 让计算机看懂"文字"

模型只能吃数字!性别"男/女",城市"北京/上海/深圳"这些类别型数据(Categorical Data)必须变成数字。但绝不能简单赋值 男=1, 女=2!为啥?模型会误会数字大小有含义(比如女 > 男?距离女-男=1?)。

  1. 独热编码(One-Hot Encoding, OHE)

    • 为什么用? 彻底消除类别间的虚假序关系。原理是为每个类别创建一个新的二进制特征(0/1)。

    • 坑:维度灾难! 如果一个类别特征有1000个不同的城市,OHE后就多了1000列!稀疏又占内存。

    • 代码: pd.get_dummies()sklearn.preprocessing.OneHotEncoder强烈建议用后者的 drop='first' 参数 避免共线性(Dummy Variable Trap)。

      python 复制代码
      # 使用 Pandas 的 get_dummies (注意避免虚拟变量陷阱,drop_first=True)
      df_encoded = pd.get_dummies(df, columns=['city', 'gender'],
      drop_first=True)  # drop_first=True 是避免陷阱的关键!新手常漏掉这个导致共线性问题
      
      # 或者用 sklearn (更规范,适合管道)
      from sklearn.preprocessing import OneHotEncoder
      ohe = OneHotEncoder(drop='first', sparse=False)  # drop='first', sparse=False 返回密集数组
      city_encoded = ohe.fit_transform(df[['city']])  # 输入必须是二维
      # 将编码后的数组转成DataFrame并合并回原数据... (此处略)
    • 适用场景: 低基数(类别数量少)的类别特征。

  2. 标签编码(Label Encoding)

    • 为什么用? 简单,只把类别映射成0,1,2,...N-1的数字。

    • 巨大坑点: 仅适用于有序类别(Ordinal)! 比如学历"小学<初中<高中<大学",数字大小有意义。对无序类别(Nominal)如城市用,就是埋雷!

    • 代码: sklearn.preprocessing.LabelEncoder

      python 复制代码
      from sklearn.preprocessing import LabelEncoder
      
      le = LabelEncoder()
      df['education_encoded'] = le.fit_transform(df['education'])  # 假设 'education' 是有序的
    • 适用场景: 明确的有序类别特征。对无序类别,慎用!

  3. 目标编码(Target Encoding / Mean Encoding)

    • 为什么用? 利用目标变量信息!用每个类别下的目标变量(如平均房价)的统计量(均值、中位数)来编码该类别。这能捕捉到类别与目标的关系。

    • 巨大优势: 能处理高基数特征,且编码值有实际意义(反映目标期望)。

    • 巨大坑点: 极易过拟合(Overfitting)!特别是数据少或某些类别样本少时。必须配合交叉验证或平滑(Smoothing)技术。

    • 代码: 常用 category_encoders 库的 TargetEncoder

      python 复制代码
      # 安装 category_encoders: pip install category_encoders
      from category_encoders import TargetEncoder
      
      # 假设我们预测 'price', 对 'neighborhood' 进行目标编码
      te = TargetEncoder(smoothing=2)  # smoothing 参数很重要,防止过拟合
      df['neighborhood_encoded'] = te.fit_transform(df['neighborhood'], df['price'])
    • 适用场景: 高基数类别特征,且能谨慎处理过拟合风险。

第三章:特征工程 - 点石成金,释放数据的洪荒之力

数据清洗和转换只是"打扫房间"和"摆放家具",特征工程才是真正的"装修设计"!它的目标是:利用领域知识,从原始数据中提取或构造出对预测目标更有信息量的特征。

为什么要做特征工程?模型性能的天花板!

数据和特征决定了模型性能的上限,模型和算法只是不断逼近这个上限。再牛的模型,喂给它一堆烂特征也无力回天。好的特征能:

  • 提升模型性能: AUC、准确率、RMSE等指标蹭蹭涨。
  • 简化模型: 用更简单的模型(如线性模型)就能达到好效果。
  • 增强可解释性: 构造的特征往往有更明确的业务含义。
特征创造:当个"数据炼金术士"
  1. 领域知识驱动:
    • 日期时间特征: 从日期中提取年、月、日、周几、季度、是否周末、是否节假日、距离某个重要日期的天数等。电商中的用户行为、交通流量预测特别需要这个。

      python 复制代码
      # 假设有 'timestamp' 列
      df['hour'] = df['timestamp'].dt.hour
      df['day_of_week'] = df['timestamp'].dt.dayofweek  # Monday=0, Sunday=6
      df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
      df['time_since_event'] = (datetime.now() - df['timestamp']).dt.days
    • 组合特征: 把几个相关特征加、减、乘、除。比如电商中"商品单价 * 购买数量 = 总金额";风控中"负债 / 收入 = 负债收入比"。

      python 复制代码
      df['bmi'] = df['weight_kg'] / (df['height_m'] ** 2)  # BMI指数 = 体重(kg) / 身高(m)^2
      df['price_per_sqft'] = df['price'] / df['area_sqft']  # 每平方英尺单价
    • 分箱(Binning / Discretization): 把连续变量分段。比如年龄分成"儿童(0-12)"、"青少年(13-18)"、"成年(19-65)"、"老年(>65)"。为什么?让模型学习非线性关系更容易,也能处理异常值。

相关推荐
@HNUSTer3 小时前
Python数据可视化科技图表绘制系列教程(六)
python·数据可视化·科技论文·专业制图·科研图表
Kevinhbr3 小时前
CSP-J/S IS COMING
数据结构·c++·算法
Moutai码农4 小时前
1.5、机器学习-回归算法
人工智能·机器学习·回归
蕓晨4 小时前
set的插入和pair的用法
c++·算法
Daisy_JuJuJu4 小时前
【科研成果速递-IJGIS】如何描述与分类移动对象的时空模式?一个新的分类框架与体系!
分类·数据挖掘·科研·运动模式·移动对象
非门由也4 小时前
《sklearn机器学习——绘制分数以评估模型》验证曲线、学习曲线
人工智能·机器学习·sklearn
THMAIL4 小时前
深度学习从入门到精通 - AutoML与神经网络搜索(NAS):自动化模型设计未来
人工智能·python·深度学习·神经网络·算法·机器学习·逻辑回归
wei_shuo4 小时前
使用 Auto-Keras 进行自动化机器学习
机器学习·自动化·keras
金古圣人4 小时前
hot100 滑动窗口
数据结构·c++·算法·leetcode·哈希算法