三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

数据清洗与转换实战:从概念到代码的完整DC2流程指南

数据清洗与转换实战:从概念到代码的完整DC2流程指南

最近在技术社区看到不少刚入行的开发者朋友在讨论“第一次做DC2”的经历,既有成功的喜悦,也踩了不少坑。DC2作为一个常见的开发任务,其核心是数据转换与清洗,看似基础,实则关系到后续数据分析、模型训练乃至整个业务逻辑的准确性。本文将从一个完整的实战项目出发,手把手带你完成一次标准的DC2流程,涵盖从需求理解、环境搭建、核心代码实现到结果验证与优化的全过程。无论你是数据科学新手,还是希望系统梳理数据预处理流程的开发者,都能从中获得可直接复用的代码和清晰的避坑指南。

1. 背景与核心概念:什么是DC2?

在开始动手之前,我们首先要明确DC2的含义。DC2通常指的是Data Cleaning and Conversion,即数据清洗与转换。它是数据预处理(Data Preprocessing)中最关键、最耗时的一环,目的是将原始、杂乱、不一致的数据转化为干净、统一、适合后续分析或建模的格式。

一个典型的DC2流程需要解决以下几类问题:

  1. 数据质量问题:如缺失值、异常值、重复记录。
  2. 格式不一致问题:如日期格式混乱(2024-01-01vs01/01/2024)、字符串大小写不统一、单位不统一(kgvsKg)。
  3. 数据转换问题:如类型转换(字符串转数值)、特征编码(分类变量转数值)、特征缩放(归一化、标准化)。
  4. 数据集成问题:合并来自多个来源的数据表。

为什么DC2如此重要?因为“垃圾进,垃圾出”(Garbage In, Garbage Out)。无论后续的算法多么高级,如果输入的数据质量低下,得到的结果也必然不可靠。掌握DC2,是每一位数据相关岗位开发者的基本功。

2. 环境准备与版本说明

本次实战我们将使用Python生态中最主流的数据处理库:pandasnumpy。它们功能强大、社区活跃,是处理DC2任务的利器。

环境要求:

  • 操作系统:Windows 10/11, macOS, 或 Linux (如Ubuntu 20.04+)。本文示例在macOS/Linux环境下编写,Windows用户请注意路径分隔符的差异(使用\)。
  • Python版本:>= 3.8。建议使用Python 3.8或3.9以获得最佳的库兼容性。
  • 核心库及版本
    • pandas >= 1.3.0
    • numpy >= 1.21.0
    • scikit-learn >= 1.0(用于部分高级转换,如标准化)

项目结构:在开始前,建议建立如下清晰的目录结构,这有助于管理代码和数据。

your_dc2_project/ ├── data/ # 存放原始数据和清洗后的数据 │ ├── raw/ # 原始数据(不要直接修改) │ │ └── sales_data_raw.csv │ └── processed/ # 清洗转换后的数据 ├── notebooks/ # (可选)Jupyter Notebook用于探索性分析 │ └── eda.ipynb ├── src/ # 源代码 │ ├── __init__.py │ ├── data_cleaner.py # 核心清洗转换逻辑 │ └── utils.py # 工具函数 ├── config.yaml # (可选)配置文件,如文件路径、参数 ├── requirements.txt # 项目依赖列表 └── main.py # 主程序入口

安装依赖:在项目根目录下创建requirements.txt文件,内容如下:

pandas==1.5.3 numpy==1.24.3 scikit-learn==1.3.0 pyyaml==6.0 # 用于读取配置文件

然后在终端中执行:

pip install -r requirements.txt

3. 核心操作与pandas API拆解

pandasDataFrame是进行DC2的核心数据结构。下面我们拆解最常用的几组操作。

3.1 数据读取与初步探查

任何DC2任务的第一步都是将数据加载到DataFrame中并进行初步了解。

import pandas as pd # 读取数据,这里以CSV为例 df = pd.read_csv(‘data/raw/sales_data_raw.csv’) # 1. 查看数据形状(行数,列数) print(f“数据形状: {df.shape}“) # 2. 查看前5行数据,了解大致内容 print(df.head()) # 3. 查看列名、数据类型和非空值数量 print(df.info()) # 4. 查看数值型列的统计摘要(计数、均值、标准差、最小/最大值、分位数) print(df.describe()) # 5. 查看唯一值数量,特别是针对分类变量 for col in df.columns: print(f“{col}: {df[col].nunique()} unique values”)

3.2 处理缺失值

缺失值处理没有“唯一正确”的方法,需要根据业务逻辑和数据性质决定。

# 检查缺失值 missing_summary = df.isnull().sum() print(“缺失值统计:”) print(missing_summary[missing_summary > 0]) # 只显示有缺失的列 # 方法1:删除缺失行(当缺失行占比很小,且缺失随机时) df_dropped = df.dropna() # 删除任何包含NaN的行 # 或指定列 df_dropped_subset = df.dropna(subset=[‘customer_id‘, ‘amount’]) # 仅当这两列有缺失时才删除行 # 方法2:填充缺失值 # 用固定值填充 df_filled_constant = df.fillna({‘category’: ‘Unknown’, ‘amount’: 0}) # 用统计值填充(如均值、中位数、众数) mean_amount = df[‘amount’].mean() df_filled_mean = df.fillna({‘amount’: mean_amount}) # 用前向或后向填充(适用于时间序列) df_filled_ffill = df.fillna(method=‘ffill’) # 用上一个有效值填充 # 方法3:插值法(对于有序数据) df_interpolated = df.interpolate(method=‘linear’) # 线性插值

3.3 处理异常值

异常值可能是错误,也可能是重要的信号,需谨慎处理。

# 常用方法:基于标准差(Z-score)或四分位距(IQR) import numpy as np def detect_outliers_iqr(df, column): “””使用IQR方法检测异常值””” Q1 = df[column].quantile(0.25) Q3 = df[column].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)] return outliers, lower_bound, upper_bound # 检测‘amount’列的异常值 outliers, low, high = detect_outliers_iqr(df, ‘amount’) print(f“异常值数量: {len(outliers)}, 边界: [{low:.2f}, {high:.2f}]“) # 处理异常值:可以剔除、替换为边界值或视为缺失值处理 # 剔除 df_no_outliers = df[(df[‘amount’] >= low) & (df[‘amount’] <= high)] # 缩尾处理(Winsorization):将超出边界的值替换为边界值 df[‘amount_winsorized’] = df[‘amount’].clip(lower=low, upper=high)

3.4 数据转换与格式化

这是DC2中创造性最强的一部分。

# 1. 类型转换 df[‘date’] = pd.to_datetime(df[‘date’], format=‘%Y-%m-%d’, errors=‘coerce’) # 日期转换 df[‘amount’] = pd.to_numeric(df[‘amount’], errors=‘coerce’) # 字符串转数值 # 2. 字符串操作 df[‘product_name’] = df[‘product_name’].str.strip() # 去除首尾空格 df[‘product_name’] = df[‘product_name’].str.lower() # 统一小写 df[‘category’] = df[‘category’].str.replace(‘&’, ‘and’) # 替换字符 # 3. 创建新特征(特征工程) df[‘year’] = df[‘date’].dt.year df[‘month’] = df[‘date’].dt.month df[‘day_of_week’] = df[‘date’].dt.dayofweek # 周一=0, 周日=6 df[‘is_weekend’] = df[‘day_of_week’].isin([5, 6]).astype(int) # 4. 分类变量编码(为机器学习准备) # 标签编码 (Label Encoding) from sklearn.preprocessing import LabelEncoder le = LabelEncoder() df[‘category_encoded’] = le.fit_transform(df[‘category’]) # 独热编码 (One-Hot Encoding) - 更常用,避免引入序关系 df_encoded = pd.get_dummies(df, columns=[‘category’], prefix=‘cat’)

4. 完整实战案例:电商销售数据清洗与转换

假设我们有一份原始的电商销售数据sales_data_raw.csv,内容杂乱。我们的目标是将其清洗为可供分析使用的干净数据集。

4.1 数据概览与问题诊断

首先,加载数据并查看原始面貌。

import pandas as pd import numpy as np # 加载数据 df_raw = pd.read_csv(‘data/raw/sales_data_raw.csv’) print(“=== 原始数据概览 ===”) print(df_raw.head(10)) print(“\n=== 原始数据信息 ===”) print(df_raw.info())

假设我们发现了以下问题:

  1. 列名有空格且大小写不一(如Order IDorder date)。
  2. order date列格式混乱,有2023/12/01Dec 1, 2023等。
  3. Amount列有字符串(如$100.50150)和缺失值(NaN)。
  4. Customer ID列有重复和空值。
  5. Product列产品名前后有空格,且同产品有不同写法(如Laptoplaptop)。

4.2 逐步清洗与转换

我们将编写一个模块化的清洗函数。

# file: src/data_cleaner.py import pandas as pd import numpy as np from datetime import datetime def clean_sales_data(df): “”” 清洗电商销售数据DataFrame 参数: df (pd.DataFrame): 原始销售数据 返回: pd.DataFrame: 清洗后的数据 “”” df_clean = df.copy() # 避免修改原始数据 # 1. 规范化列名 df_clean.columns = df_clean.columns.str.strip().str.lower().str.replace(‘ ‘, ‘_’) print(“步骤1: 列名已规范化。”) # 2. 处理‘order_date‘:尝试多种日期格式解析 df_clean[‘order_date’] = pd.to_datetime(df_clean[‘order_date’], errors=‘coerce’, infer_datetime_format=True) # 检查转换失败的日期 failed_dates = df_clean[df_clean[‘order_date’].isna()] if not failed_dates.empty: print(f“警告: {len(failed_dates)} 行日期无法解析,已设为NaT。”) # 可以在这里添加更复杂的逻辑来处理特定格式 # 3. 处理‘amount‘:移除货币符号,转换为浮点数 # 先确保是字符串类型 df_clean[‘amount’] = df_clean[‘amount’].astype(str) df_clean[‘amount’] = df_clean[‘amount’].str.replace(‘$’, ‘’, regex=False).str.replace(‘,’, ‘’, regex=False) df_clean[‘amount’] = pd.to_numeric(df_clean[‘amount’], errors=‘coerce’) print(“步骤3: ‘amount‘列已转换为数值。”) # 4. 处理‘customer_id‘:去除空格,填充缺失值为‘UNKNOWN_’+索引 df_clean[‘customer_id’] = df_clean[‘customer_id’].astype(str).str.strip() # 将空字符串或‘nan‘字符串视为缺失 mask_missing_id = (df_clean[‘customer_id’].isin([‘’, ‘nan’, ‘NaN’, ‘None’])) | (df_clean[‘customer_id’].isna()) df_clean.loc[mask_missing_id, ‘customer_id’] = [f’UNKNOWN_{i}‘ for i in range(mask_missing_id.sum())] print(f“步骤4: 已处理 {mask_missing_id.sum()} 个缺失的customer_id。”) # 5. 处理‘product‘:去除首尾空格,统一小写 df_clean[‘product’] = df_clean[‘product’].astype(str).str.strip().str.lower() # 简单的拼写纠正(示例) product_mapping = {‘laptop‘: ‘laptop’, ‘notebook‘: ‘laptop’, ‘mobile‘: ‘phone’, ‘cell phone‘: ‘phone’} df_clean[‘product’] = df_clean[‘product’].replace(product_mapping) print(“步骤5: ‘product‘列已清洗和标准化。”) # 6. 处理缺失值:金额用中位数填充(比均值对异常值更鲁棒) if df_clean[‘amount’].isna().any(): median_amount = df_clean[‘amount’].median() df_clean[‘amount’].fillna(median_amount, inplace=True) print(f“步骤6: 用中位数 {median_amount:.2f} 填充了 {df_clean[‘amount’].isna().sum()} 个缺失金额。”) # 7. 删除完全重复的行(所有列值都相同) initial_rows = len(df_clean) df_clean.drop_duplicates(inplace=True) dropped_rows = initial_rows - len(df_clean) print(f“步骤7: 删除了 {dropped_rows} 个完全重复的行。”) # 8. 创建衍生特征 df_clean[‘order_year’] = df_clean[‘order_date’].dt.year df_clean[‘order_month’] = df_clean[‘order_date’].dt.month df_clean[‘order_quarter’] = df_clean[‘order_date’].dt.quarter df_clean[‘day_of_week’] = df_clean[‘order_date’].dt.dayofweek # Monday=0 df_clean[‘is_weekend’] = (df_clean[‘day_of_week’] >= 5).astype(int) print(“步骤8: 已创建时间衍生特征。”) # 9. 重置索引 df_clean.reset_index(drop=True, inplace=True) print(“\n=== 清洗完成 ===") print(f“原始数据行数: {len(df)}”) print(f“清洗后数据行数: {len(df_clean)}”) print(f“清洗后列信息:”) print(df_clean.info()) return df_clean

4.3 运行与验证

创建一个主程序来调用清洗函数并保存结果。

# file: main.py import pandas as pd from src.data_cleaner import clean_sales_data import os def main(): # 1. 读取原始数据 input_path = ‘data/raw/sales_data_raw.csv’ if not os.path.exists(input_path): print(f“错误: 未找到原始数据文件 {input_path}”) return df_raw = pd.read_csv(input_path) print(“成功加载原始数据。”) # 2. 执行清洗 df_clean = clean_sales_data(df_raw) # 3. 保存清洗后的数据 output_dir = ‘data/processed/’ os.makedirs(output_dir, exist_ok=True) # 确保目录存在 output_path = os.path.join(output_dir, ‘sales_data_clean.csv’) df_clean.to_csv(output_path, index=False) print(f“\n清洗后的数据已保存至: {output_path}”) # 4. 简单验证 print(“\n=== 数据验证 ===") print(“前5行清洗后数据:”) print(df_clean.head()) print(“\n基本统计信息:”) print(df_clean[[‘amount’, ‘order_year’, ‘order_month’]].describe()) if __name__ == “__main__”: main()

在终端运行:

python main.py

4.4 结果说明

运行上述脚本后,你将在data/processed/sales_data_clean.csv中得到一个清洗后的数据文件。与原始数据相比,它具有:

  • 规范的列名:全部小写并用下划线连接。
  • 统一的日期格式order_date列已成为datetime64类型。
  • 干净的数值amount列已是纯数值类型,缺失值已被合理填充。
  • 标准化的文本productcustomer_id列已无多余空格和大小写问题。
  • 丰富的特征:新增了年、月、季度、是否周末等衍生列,便于后续按时间维度分析。
  • 无完全重复行:保证了数据的唯一性。

5. 常见问题与排查思路

在DC2过程中,你可能会遇到以下典型问题:

问题现象可能原因排查与解决思路
pd.read_csvUnicodeDecodeError文件编码不是默认的utf-8尝试指定编码,如encoding=‘gbk’encoding=‘latin1’encoding=‘utf-8-sig’。用chardet库检测文件编码。
日期列转换后全部变成NaT日期格式与pandas推断的格式不匹配。使用pd.to_datetime(df[‘col’], format=‘%Y/%m/%d’)明确指定格式。或用errors=‘coerce’找出无法转换的原始值进行单独处理。
数值列转换后出现大量NaN列中混入了非数字字符(如货币符号、逗号、文字)。先使用.astype(str)转为字符串,再用.str.replace()移除特殊字符,最后用pd.to_numeric(errors=‘coerce’)转换。
内存使用量激增,程序变慢数据集过大;操作产生了不必要的中间副本。1. 读取时指定dtype参数。2. 使用df[df[‘col’] > 0].copy()而非df[df[‘col’] > 0]来避免链式索引警告。3. 对于超大文件,考虑分块读取 (chunksize)。
分组或聚合结果不符合预期分组键中存在意料之外的空格、大小写或数据类型不一致。在分组前,对作为键的列执行.str.strip().str.lower().astype(str)进行标准化。使用df[‘col’].unique()查看唯一值。
清洗后数据行数意外减少dropna()drop_duplicates()条件过于严格;合并操作丢失了数据。仔细检查每个删除或过滤步骤前后的行数。对于dropna,使用subset参数指定关键列。对于合并,确认使用的是innerleftright还是outerjoin。

6. 最佳实践与工程建议

将DC2从一次性的脚本升级为可维护、可复用的工程代码,需要遵循以下实践:

  1. 保持原始数据不可变:永远在数据的副本(df.copy())上进行操作,并保留原始文件。这让你可以随时回溯和重新开始。
  2. 模块化与函数化:如案例所示,将清洗逻辑封装在独立的函数或类中。这提高了代码的可读性、可测试性和复用性。可以为不同的数据源编写不同的清洗器。
  3. 配置化参数:将文件路径、填充值、映射字典(如产品名映射)等提取到配置文件(如config.yamlconfig.py)中。这样无需修改代码即可调整清洗逻辑。
  4. 记录与日志:在清洗函数中加入详细的print语句或使用logging模块记录每一步的操作和影响(如“删除了X行重复数据”)。这对于调试和审计至关重要。
  5. 单元测试:为你的核心清洗函数编写单元测试。使用一个小型的、包含各种“脏数据”的样例数据集,验证函数输出是否符合预期。pytest是很好的选择。
  6. 处理大数据集:如果数据量极大(超过内存),考虑使用DaskModin库,它们提供了类似pandas的API但支持并行和核外计算。或者,使用数据库(如SQLitePostgreSQL)进行清洗。
  7. 自动化与流水线:使用MakefileApache AirflowPrefect等工具将数据清洗任务自动化、调度化,形成可重复的数据流水线。
  8. 版本控制数据:对于重要的中间数据和最终产出,考虑使用DVC(Data Version Control) 进行版本管理,将数据和代码变更关联起来。

第一次做DC2,核心是建立起清晰、稳健的处理流程。从理解数据开始,分步骤、模块化地解决每一个质量问题,并始终牢记验证结果。本文提供的案例和代码是一个完整的起点,你可以将其作为模板,根据自己项目的具体需求进行修改和扩展。数据清洗是一项需要耐心和细心的工作,但每一次成功的DC2都会让你对数据的理解更深一步,为后续更有价值的分析和建模打下坚实的基础。动手运行一遍代码,替换成你自己的数据集,体验从混乱到有序的全过程吧。

← 返回列表