Skip to main content

数据预处理

想象一下你要做一道美味的菜肴(比如“宫保鸡丁”),而你手头有一些原始的食材(数据)。直接把原始食材扔进锅里炒,很可能做出来的东西不好吃或者根本不能吃。你需要对食材进行一系列处理,这就是数据预处理。


1. 数据清洗 (Data Cleaning)

  • 通俗例子:

    • 挑拣食材: 你拿到一把青菜,发现里面有些烂叶子、小虫子或者泥土。你需要把这些坏的、脏的、不需要的东西挑出来扔掉
    • 处理不一致: 你发现有些鸡丁切得特别大,有些特别小,或者有些食谱记录的辣椒单位是“个”,有些是“克”,你需要把它们统一一下。
  • 对应数据概念:

    • 处理错误值/异常值 (Errors/Outliers): 数据中可能存在明显不合理的值,比如一个人的年龄是200岁,或者温度记录是-1000度。这些可能是输入错误或传感器故障。需要识别并修正或删除它们。
    • 处理重复数据 (Duplicates): 同一条记录出现了多次,需要去重。
    • 处理不一致性 (Inconsistencies): 相同含义的数据有不同的表示方式,比如性别记录有“男”、“M”、“Male”,需要统一成一种。或者单位不一致。
  • 一句话理解: 把数据中“脏”的、“坏的”、“乱的”部分弄干净、弄整齐。


2. 缺失值处理 (Handling Missing Values)

  • 通俗例子:

    • 食材缺失: 你发现食谱里需要花生米,但是你家里没有花生米了。
    • 怎么办?
      • 不放了 (删除): 如果花生米不是这道菜的关键,或者你只有一丁点其他食材,干脆就不放花生米了(如果缺失值太多,可能删除整条记录或整个特征)。
      • 找替代品 (填充 - 均值/中位数/众数): 你可以用腰果代替花生米,味道可能也还行(用现有数据的平均值、中位数或出现最多的值来填充缺失值)。
      • 精确估计 (填充 - 模型预测): 你根据其他食材的搭配,推测出用什么坚果替代花生米效果最好(使用机器学习模型来预测缺失值)。
  • 对应数据概念:

    • 当数据某些特征的数值丢失或未记录时,就产生了缺失值。
    • 处理方法:
      • 删除:
        • 删除带有缺失值的行(样本)。
        • 删除带有过多缺失值的列(特征)。
      • 填充 (Imputation):
        • 均值/中位数/众数填充: 用该特征的平均值、中位数(对数值型数据)或众数(对类别型数据)填充。
        • 固定值填充: 用一个特定的值(如0, -1, "Unknown")填充。
        • 预测模型填充: 使用其他特征作为输入,通过机器学习模型(如回归、KNN)来预测缺失值。
        • 前后值填充: 对于时间序列数据,可以用前一个时间点或后一个时间点的值填充。
  • 一句话理解: 食材不够了,想办法补上或者干脆不用。


3. 特征工程 (Feature Engineering)

  • 通俗例子:

    • 食材的深加工和搭配:
      • 切菜 (转换/创建新特征): 你把整块鸡胸肉切成鸡丁,把黄瓜切成小块。这使得食材更易于烹饪和入味。(比如从“出生日期”这个特征中提取出“年龄”或“星期几出生”)。
      • 腌制 (交互特征): 你把鸡丁用酱油、料酒、淀粉腌制一下,这样鸡丁会更嫩滑,味道更好。(比如把“身高”和“体重”组合起来计算“BMI指数”)。
      • 调酱汁 (组合特征): 你把酱油、醋、糖、盐等按一定比例混合调制成宫保鸡丁的酱汁,这个酱汁是这道菜的灵魂。(比如将多个原始特征通过某种函数组合成一个新的、更有意义的特征)。
      • 判断季节 (独热编码): 如果食谱根据季节不同用料有差异,你会标记出当前是“春季”、“夏季”、“秋季”还是“冬季”中的哪一个,而不是用1,2,3,4这种有大小关系的数字表示。(将类别型特征转换为多个0/1的二元特征)。
  • 对应数据概念:

    • 从原始数据中提取、转换或创造出新的、对模型预测更有帮助的特征的过程。这是机器学习中非常重要且最具创造性的一步。
    • 常见方法:
      • 转换 (Transformation): 对数转换、平方根转换等,改变数据分布。
      • 创建 (Creation):
        • 从日期时间中提取年、月、日、星期、小时等。
        • 计算比例、差值、和、积等。
        • 基于业务理解构建有意义的指标(如用户活跃度、商品热度)。
      • 交互特征 (Interaction Features): 将两个或多个特征组合起来(如相乘、相除)。
      • 独热编码 (One-Hot Encoding): 将类别型特征转换为数值型,每个类别成为一个新的二元特征。
      • 分箱/离散化 (Binning/Discretization): 将连续型特征划分为若干个区间(“箱子”),转换为类别型特征。
  • 一句话理解: 把原始食材加工处理成更容易烹饪、更能提升菜品风味(模型性能)的“半成品”或“调料”。


4. 特征选择 (Feature Selection)

  • 通俗例子:

    • 挑选关键调料: 你准备做宫保鸡丁,桌上摆满了各种调料:盐、糖、醋、酱油、花椒、八角、桂皮、香叶等等。
    • 你知道做宫保鸡丁,盐、糖、醋、酱油、花椒是必须的,但八角、桂皮、香叶可能不太适合这道菜,放了反而味道怪怪的,或者没啥用还占地方。所以你只选择那些对最终味道(模型预测)真正有贡献的调料
  • 对应数据概念:

    • 从所有可用的特征中,选择出一个最优的子集,用于训练模型。
    • 目的:
      • 简化模型,使其更易于理解。
      • 减少训练时间。
      • 避免维度灾难。
      • 提高模型泛化能力,防止过拟合(通过去除不相关或冗余的特征)。
    • 常用方法分类:
      • 过滤法 (Filter Methods): 根据特征本身的统计特性(如方差、相关系数、卡方检验、信息增益)进行评分和筛选,独立于后续的学习器。速度快。
      • 包裹法 (Wrapper Methods): 将特征选择过程看作是一个搜索问题,通过不断地选择特征子集,用学习器的性能(如准确率)作为评价标准。效果好但计算量大。例如:递归特征消除 (RFE)。
      • 嵌入法 (Embedded Methods): 特征选择过程嵌入到模型训练过程中。例如:LASSO回归 (L1正则化) 会将不重要特征的系数压缩为0,决策树在分裂时也会进行特征选择。
  • 一句话理解: 从一堆“调料”(特征)中,挑出那些真正能让菜品(模型)更好吃的关键调料。


5. 数据归一化/标准化 (Data Normalization/Standardization)

  • 通俗例子:

    • 统一计量单位: 你在看一份国际食谱,里面有的食材用“盎司”,有的用“克”,有的用“磅”。如果你不把它们统一成一个你熟悉的单位(比如都换算成“克”),你很难准确地配料。
    • 调整食材“分量感”: 想象你要比较“一粒芝麻的重量”和“一个西瓜的重量”。如果直接用原始重量比较,西瓜的重量会完全“压倒”芝麻的重量,让你忽略芝麻的存在。你需要把它们的“分量感”调整到差不多的范围,才能公平比较它们在菜品中的相对重要性(对于某些算法而言)。
  • 对应数据概念:

    • 将不同尺度(范围和单位)的数值型特征转换到一个相似的尺度上。这对于很多依赖于距离计算或梯度下降的算法(如KNN、SVM、神经网络、线性回归、逻辑回归)非常重要。
    • 主要方法:
      • 归一化 (Normalization / Min-Max Scaling):
        • 公式: X_norm = (X - X_min) / (X_max - X_min)
        • 将数据缩放到一个固定的区间,通常是 [0, 1][-1, 1]
        • 优点: 保持了原始数据的分布形状。
        • 缺点: 对异常值非常敏感,因为 X_minX_max 会被异常值影响。
      • 标准化 (Standardization / Z-score Normalization):
        • 公式: X_std = (X - μ) / σ (其中 μ 是均值,σ 是标准差)
        • 将数据转换为均值为0,标准差为1的标准正态分布(或接近标准正态分布)。
        • 优点: 对异常值的鲁棒性比归一化好一些。是更常用的方法。
        • 缺点: 改变了原始数据的分布,不再保留原始的最小值和最大值。
  • 一句话理解: 把不同“个头”(数值范围)的食材调整到差不多“大小”,方便算法“一视同仁”地处理它们。


总结一下这道“宫保鸡丁”的制作流程(数据预处理流程):

  1. 数据清洗 (挑拣食材): 把烂掉的鸡肉、发霉的花生米扔掉。
  2. 缺失值处理 (食材缺失): 没花生米了,用腰果代替。
  3. 特征工程 (食材深加工): 鸡肉切丁、腌制,黄瓜切块,调制酱汁。
  4. 特征选择 (挑选关键调料): 确定用盐、糖、醋、花椒,不用八角桂皮。
  5. 数据归一化/标准化 (统一计量单位): 确保所有调料的用量都是以“克”为单位,而不是有的用“勺”,有的用“撮”,方便精确配比。

经过这些预处理步骤,你的“食材”(数据)就准备好了,可以放心地交给“厨师”(机器学习模型)去烹饪美味的“菜肴”(做出准确的预测或分析)了!、

好的,我们来详细介绍一个典型的数据预处理工作流程,并说明在这个流程中如何结合使用 Pandas、NumPy、Scikit-learn 和 PyTorch (主要在最后阶段)。

数据预处理工作流程 (结合常用库)


阶段一:数据加载与初步探索 (主要使用 Pandas, NumPy)

  1. 加载数据 (Pandas)

    • 库操作: pd.read_csv(), pd.read_excel(), pd.read_sql(), pd.read_json() 等。
    • 目的: 将原始数据读入到 Pandas DataFrame 中,这是后续操作的基础。
    • 例子:
      import pandas as pd
      df = pd.read_csv('your_data.csv')
      
  2. 理解数据结构与基本信息 (Pandas)

    • 库操作:
      • df.head(), df.tail(): 查看数据的前几行和后几行。
      • df.shape: 查看数据的维度 (行数, 列数)。
      • df.info(): 查看每列的数据类型、非空值数量、内存占用等概览信息。
      • df.dtypes: 查看每列的数据类型。
      • df.describe(): 对数值型列生成描述性统计(计数、均值、标准差、最小值、最大值、四分位数)。
      • df.columns: 查看所有列名。
      • df['column_name'].value_counts(): 查看类别型特征的取值分布。
    • 目的: 快速了解数据的大致情况,有哪些特征,特征是什么类型,有没有明显的缺失等。
  3. 数据可视化初步探索 (Pandas, Matplotlib, Seaborn)

    • 库操作:
      • Pandas 内置绘图: df['column'].hist(), df.plot(kind='scatter', x='col1', y='col2')
      • Matplotlib: 更底层的绘图库,提供更多自定义选项。
      • Seaborn: 基于 Matplotlib 的高级统计绘图库,图形更美观,API 更简洁。sns.histplot(), sns.boxplot(), sns.scatterplot(), sns.heatmap() (用于看相关性)。
    • 目的: 通过可视化更直观地发现数据的分布、异常值、特征间的关系等。

阶段二:数据清洗 (主要使用 Pandas, NumPy)

  1. 处理缺失值 (Pandas)

    • 识别: df.isnull().sum() (统计每列缺失值数量)。
    • 处理策略:
      • 删除 (Pandas):
        • df.dropna(axis=0): 删除包含缺失值的行。
        • df.dropna(axis=1): 删除包含缺失值的列。
        • df.dropna(subset=['column_name']): 删除特定列有缺失值的行。
      • 填充 (Pandas):
        • df['column'].fillna(df['column'].mean()) (均值填充)
        • df['column'].fillna(df['column'].median()) (中位数填充)
        • df['column'].fillna(df['column'].mode()[0]) (众数填充,注意 mode() 可能返回多个值)
        • df['column'].fillna(0) (用固定值填充)
        • df['column'].ffill() (前向填充), df['column'].bfill() (后向填充)
      • 填充 (Scikit-learn - 更高级的填充,通常在特征工程后进行,因为依赖其他特征):
        • from sklearn.impute import SimpleImputer, KNNImputer (在数据转为 NumPy 数组后使用)
    • 目的: 确保数据完整性,避免因缺失值导致模型出错或结果有偏。
  2. 处理重复值 (Pandas)

    • 识别: df.duplicated().sum()
    • 处理: df.drop_duplicates(keep='first', inplace=True) (保留第一个出现的,删除后续重复项)
    • 目的: 避免因重复样本影响模型训练的公平性和效率。
  3. 处理异常值/错误值 (Pandas, NumPy, Scipy.stats)

    • 识别:
      • 可视化: 箱线图 (sns.boxplot()),散点图。
      • 统计方法:
        • Z-score: from scipy.stats import zscore; z_scores = np.abs(zscore(df_numeric)) (通常大于2或3的认为是异常值)。
        • IQR (Interquartile Range) 方法: Q1 = df['col'].quantile(0.25); Q3 = df['col'].quantile(0.75); IQR_val = Q3 - Q1; lower_bound = Q1 - 1.5 * IQR_val; upper_bound = Q3 + 1.5 * IQR_val.
    • 处理策略:
      • 删除: 如果异常值是明显的错误且数量不多。
      • 修正: 如果能确定正确的值。
      • 替换/盖帽 (Capping): 用上下限替换超出范围的值。df['col'] = np.clip(df['col'], lower_bound, upper_bound)
      • 分箱: 将异常值归入特定的“箱子”。
      • 视为缺失值处理。
    • 目的: 减少极端值对模型训练(尤其是基于距离或方差的算法)的不良影响。
  4. 数据类型转换 (Pandas)

    • 库操作: df['column'] = df['column'].astype(new_type) (如 float, int, str, category)
    • 目的: 确保特征以正确的类型存储和处理(例如,数值型特征用于计算,类别型特征用于编码)。

阶段三:特征工程 (主要使用 Pandas, Scikit-learn, NumPy)

  1. 特征创建/转换 (Pandas, NumPy)

    • 从现有特征派生新特征:
      • 日期时间特征提取: df['year'] = df['date_column'].dt.year
      • 文本特征长度: df['text_len'] = df['text_column'].apply(len)
      • 比例/组合: df['feat_ratio'] = df['feat1'] / df['feat2']
      • 多项式特征 (Scikit-learn): from sklearn.preprocessing import PolynomialFeatures; poly = PolynomialFeatures(degree=2); df_poly = poly.fit_transform(df_numeric)
    • 对数/平方根等变换 (NumPy): df['log_feat'] = np.log1p(df['feat']) (log1p 处理0值)
    • 业务驱动的特征: 根据对业务的理解创造有意义的指标。
    • 目的: 提取更多有用的信息,使数据更适合模型学习。
  2. 编码类别特征 (Pandas, Scikit-learn)

    • 标签编码 (Label Encoding - Scikit-learn):
      • from sklearn.preprocessing import LabelEncoder; le = LabelEncoder(); df['cat_encoded'] = le.fit_transform(df['categorical_feat'])
      • 适用于序数类别特征或树模型的目标变量。
    • 独热编码 (One-Hot Encoding - Pandas 或 Scikit-learn):
      • Pandas: pd.get_dummies(df, columns=['categorical_feat_1', 'categorical_feat_2'])
      • Scikit-learn: from sklearn.preprocessing import OneHotEncoder; ohe = OneHotEncoder(sparse_output=False); encoded_data = ohe.fit_transform(df[['categorical_feat']])
      • 适用于名义类别特征,避免引入错误的序数关系。
    • 目标编码 (Target Encoding - 需要自定义或使用 category_encoders 库): 根据目标变量的均值对类别进行编码。
    • 目的: 将非数值的类别数据转换为模型可以处理的数值形式。
  3. 特征缩放 (数据归一化/标准化 - Scikit-learn)

    • 标准化 (Standardization):
      • from sklearn.preprocessing import StandardScaler; scaler = StandardScaler(); df_scaled_numeric = scaler.fit_transform(df_numeric)
    • 归一化 (Normalization):
      • from sklearn.preprocessing import MinMaxScaler; scaler = MinMaxScaler(); df_scaled_numeric = scaler.fit_transform(df_numeric)
    • 通常只对数值型特征进行缩放。
    • 目的: 消除不同特征尺度(量纲)的影响,使基于距离或梯度的算法能更好地工作。
  4. 特征选择/降维 (Scikit-learn)

    • 过滤法:
      • 方差阈值: from sklearn.feature_selection import VarianceThreshold; sel = VarianceThreshold(threshold=(.8 * (1 - .8))); df_reduced = sel.fit_transform(df_numeric)
      • 相关性分析 (Pandas): df.corr() (然后手动或基于阈值移除高度相关的特征)。
      • 卡方/F检验/互信息: from sklearn.feature_selection import SelectKBest, chi2, f_classif, mutual_info_classif
    • 包裹法:
      • 递归特征消除: from sklearn.feature_selection import RFE; from sklearn.linear_model import LogisticRegression; estimator = LogisticRegression(); selector = RFE(estimator, n_features_to_select=5, step=1)
    • 嵌入法:
      • L1正则化 (Lasso): from sklearn.linear_model import Lasso; from sklearn.feature_selection import SelectFromModel
      • 树模型的特征重要性: model.feature_importances_
    • 降维 (PCA - Scikit-learn):
      • from sklearn.decomposition import PCA; pca = PCA(n_components=0.95); df_pca = pca.fit_transform(df_scaled_numeric)
    • 目的: 移除不相关或冗余的特征,减少模型复杂度,提高泛化能力,加速训练。

阶段四:数据分割与准备模型输入 (Scikit-learn, PyTorch)

  1. 分割数据集 (Scikit-learn)

    • 库操作: from sklearn.model_selection import train_test_split
    • X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) (如果是分类问题,stratify=y 保持类别比例)
    • 目的: 将数据分为训练集、验证集(可选,可从训练集中再分)和测试集,用于模型训练、调优和评估。
  2. 转换为模型所需的格式 (NumPy, PyTorch)

    • 确保数据是数值型的 NumPy 数组: Scikit-learn 的许多模型和预处理步骤的输出已经是 NumPy 数组。如果还是 Pandas DataFrame,可以用 df.values 转换。
    • 对于 PyTorch 模型:
      • 将 NumPy 数组转换为 PyTorch 张量: torch.from_numpy(numpy_array).float()
      • 创建自定义 Dataset 类:
        import torch
        from torch.utils.data import Dataset, DataLoader
        
        class CustomDataset(Dataset):
            def __init__(self, features, labels):
                self.features = torch.from_numpy(features).float()
                self.labels = torch.from_numpy(labels).float() # or .long() for classification labels
        
            def __len__(self):
                return len(self.features)
        
            def __getitem__(self, idx):
                return self.features[idx], self.labels[idx]
        
        train_dataset = CustomDataset(X_train_processed, y_train)
        train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
        
    • 目的: 使数据格式符合所选机器学习库或深度学习框架的要求。

重要注意事项:

  • 顺序: 特征缩放、编码等操作通常在数据集分割之后进行,并且 fit(学习参数,如均值、标准差、最大最小值、编码映射)只在训练集上进行,然后用学习到的参数 transform 训练集、验证集和测试集,以避免数据泄露。
  • 迭代: 数据预处理是一个迭代的过程。你可能需要多次返回之前的步骤,根据模型的表现或新的理解来调整预处理策略。
  • 业务理解: 对业务场景和数据的深入理解是进行有效数据预处理的关键。
  • 自动化与管道 (Pipeline - Scikit-learn):
    • from sklearn.pipeline import Pipeline
    • from sklearn.compose import ColumnTransformer (对不同类型的列应用不同的转换)
    • 使用 Pipeline 可以将多个预处理步骤和模型串联起来,使代码更简洁、可维护,并能正确处理交叉验证中的数据泄露问题。

这个流程提供了一个通用的框架。在实际项目中,你可能只需要其中的一部分步骤,或者需要更复杂的特定领域预处理技术。