数据采集与清洗

机器学习专题 · 掌握数据获取与清洗的完整技术

专题:Python机器学习系统学习

关键词:Python, 机器学习, 数据采集, 数据清洗, 缺失值处理, 异常值检测, Pandas, 数据预处理

一、数据来源与采集

在机器学习项目中,数据采集是第一步也是至关重要的一步。高质量的数据是构建优秀模型的基础。数据采集的方式多种多样,根据项目需求和数据来源的不同,我们需要选择合适的方法来获取数据。

1. 公开数据集

对于学习和原型验证阶段,公开数据集是最便捷的数据来源。Kaggle 是全球最大的数据科学社区,提供了大量的竞赛数据集和公共数据集,涵盖从图像分类到时间序列预测的各个领域。UCI Machine Learning Repository 是历史最悠久的机器学习数据集仓库,许多经典论文都基于这些数据集进行实验。Government Data 平台(如 data.gov、data.gov.cn)提供了政府公开的统计数据、气象数据、经济数据等。HuggingFace Datasets 是一个快速增长的数据集库,尤其在自然语言处理领域资源丰富,支持一键加载和预处理。

2. API 数据采集

许多在线服务提供 RESTful API 接口,允许开发者以编程方式获取数据。常见的 API 包括 Twitter API(获取推文进行情感分析)、GitHub API(获取仓库信息进行开发者行为分析)、天气 API(获取气象数据进行预测)等。使用 API 采集数据时,通常需要注册获取 API Key,了解请求频率限制(Rate Limit),并处理分页返回的结果。Python 中的 requests 库是进行 API 调用的标准工具。

import requests import json # 调用 RESTful API 获取数据 url = "https://api.example.com/v1/data" headers = {"Authorization": "Bearer YOUR_API_KEY"} params = {"limit": 100, "page": 1} response = requests.get(url, headers=headers, params=params) if response.status_code == 200: data = response.json() print(f"获取到 {len(data['results'])} 条记录") else: print(f"请求失败: {response.status_code}")

3. Web 爬虫

当目标网站没有提供 API 时,Web 爬虫是获取数据的有效手段。Python 生态中有两个主流的爬虫工具:Requests + BeautifulSoup 组合适合中小规模的静态网页爬取,Scrapy 框架则适合大规模、高性能的爬虫项目。编写爬虫时需要遵守 robots.txt 协议,控制请求频率以免对目标服务器造成压力,并注意数据使用的合法性。

import requests from bs4 import BeautifulSoup url = "https://example.com/data-page" response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser") # 提取所有表格行数据 rows = soup.select("table tr") for row in rows[1:]: # 跳过表头 cells = row.find_all("td") if len(cells) >= 3: print(cells[0].text, cells[1].text, cells[2].text)

4. 数据库数据提取

企业环境中的数据通常存储在关系型数据库(如 MySQL、PostgreSQL)或 NoSQL 数据库(如 MongoDB)中。通过 SQL 查询可以从关系型数据库中提取所需的数据子集,而 MongoDB 则使用类似 JSON 的查询语法。Python 的 sqlite3 模块内置了对 SQLite 的支持,SQLAlchemy 则提供了统一的数据库操作接口。

5. 文件数据导入

数据也常以文件形式存在。CSV 是最通用的表格数据格式,几乎所有的数据分析工具都支持。Excel 文件在企业中广泛使用,但需要注意格式和编码问题。JSON 适合存储嵌套结构的数 据,常用于 Web API 的数据交换。Parquet 是一种列式存储格式,在大数据场景下性能优异,文件体积小,读写速度快。

6. 数据采集伦理与法律

数据采集必须遵守法律法规和伦理规范。需要重点关注以下几个方面:尊重数据版权和知识产权,获取数据前确认使用条款;保护个人隐私,对敏感信息进行脱敏处理;遵守 GDPR、网络安全法等法律法规;在学术研究和商业应用中,明确标注数据来源;对于爬虫采集的数据,遵守 robots.txt 协议和服务条款。违反数据采集规范可能导致法律诉讼和声誉损失。

二、Pandas 数据读取

Pandas 是 Python 中最强大的数据处理库,提供了丰富的数据读取函数,能够从各种数据源中加载数据。掌握 Pandas 的数据读取技巧是数据清洗的前提。

1. read_csv() 参数详解

read_csv() 是 Pandas 中最常用的函数,用于读取 CSV 文件。它的参数非常丰富,可以应对各种格式变体。常用参数包括:sep 或 delimiter 指定分隔符(默认为逗号);header 指定表头行;names 手动指定列名;index_col 指定索引列;usecols 选择读取的列;dtype 指定列的数据类型;parse_dates 自动解析日期列;encoding 指定文件编码(常用 utf-8、gbk)。

import pandas as pd # 基本读取 df = pd.read_csv("data.csv") # 高级参数示例 df = pd.read_csv( "data.csv", sep=",", encoding="utf-8", header=0, index_col="id", usecols=["id", "name", "age", "salary"], dtype={"age": int, "salary": float}, parse_dates=["join_date"], na_values=["NA", "NULL", "-", ""] )

2. 读取 Excel 多工作表

Excel 文件通常包含多个工作表,pd.read_excel() 可以通过 sheet_name 参数指定要读取的工作表名称或索引,也可以传入列表同时读取多个工作表返回一个字典。

# 读取单个工作表 df = pd.read_excel("report.xlsx", sheet_name="Sheet1") # 读取多个工作表 dfs = pd.read_excel("report.xlsx", sheet_name=["Sales", "Inventory"]) sales_df = dfs["Sales"] # 读取所有工作表 all_sheets = pd.read_excel("report.xlsx", sheet_name=None) # 指定列类型和表头行 df = pd.read_excel("report.xlsx", sheet_name=0, header=1, dtype=str)

3. JSON 数据解析

JSON 数据常见于 Web API 的返回结果。pd.read_json() 可以直接读取 JSON 文件或 JSON 格式的字符串,也支持嵌套 JSON 的规范化(json_normalize)。

# 从文件读取 JSON df = pd.read_json("data.json") # 从 JSON 字符串读取 json_str = '[{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}]' df = pd.read_json(json_str) # 处理嵌套 JSON from pandas.json_normalize import json_normalize nested_data = [ {"id": 1, "info": {"city": "Beijing", "age": 28}}, {"id": 2, "info": {"city": "Shanghai", "age": 32}} ] df = json_normalize(nested_data) # 结果列: id, info.city, info.age

4. 大文件分块读取

当数据集非常大,无法一次性载入内存时,可以使用 chunksize 参数分块读取数据。这会返回一个迭代器,每次读取指定行数的数据块,处理后释放内存。

# 分块读取大文件 chunk_size = 10000 total_rows = 0 total_sum = 0.0 for chunk in pd.read_csv("large_dataset.csv", chunksize=chunk_size): total_rows += len(chunk) total_sum += chunk["value"].sum() print(f"已处理 {total_rows} 行...") print(f"总计: {total_rows} 行, 总和: {total_sum:.2f}")

5. 数据库连接读取

Pandas 的 read_sql() 函数可以直接从数据库中读取数据。需要配合数据库连接引擎(如 SQLAlchemy)使用,支持复杂的 SQL 查询语句。

from sqlalchemy import create_engine # 创建数据库连接 engine = create_engine("sqlite:///database.db") # 使用 SQL 查询读取数据 query = "SELECT customer_id, order_date, amount FROM orders WHERE amount > 100" df = pd.read_sql(query, engine) # 读取整个表 df_products = pd.read_sql("SELECT * FROM products", engine) # 使用表名直接读取 df_customers = pd.read_sql_table("customers", engine)

三、数据探索与概览

在开始数据清洗之前,首先需要对数据有一个全面的了解。Pandas 提供了一系列便捷的方法来快速探索数据的整体情况。

head() 和 tail() 方法可以查看数据的前几行和后几行,快速了解数据的列结构和样本内容。info() 方法会显示 DataFrame 的摘要信息,包括列名、非空值数量、数据类型和内存使用情况,是初步了解数据质量的关键工具。describe() 方法生成数值型列的统计摘要,包括计数、均值、标准差、最小值、四分位数和最大值,帮助快速把握数据的分布特征。

value_counts() 方法用于查看类别型数据的分布情况,可以添加 normalize=True 参数显示比例而非绝对计数。corr() 方法计算数值列之间的相关系数矩阵,皮尔逊相关系数可以帮助发现变量之间的线性关系。shape 属性返回 DataFrame 的行数和列数,是最直观的数据维度查看方式。

# 数据探索常用操作 print("数据形状:", df.shape) print("\n前5行:") print(df.head()) print("\n数据类型概览:") print(df.info()) print("\n统计摘要:") print(df.describe()) print("\n类别分布:") print(df["category"].value_counts()) print("\n相关性矩阵:") print(df.corr())

四、缺失值处理

缺失值是数据清洗中最常见的问题之一。缺失值的产生原因多种多样,包括数据采集过程中的遗漏、传感器故障、人为录入错误等。正确处理缺失值是保证模型质量的关键环节。

1. 识别缺失值

isnull() 和 isna() 方法可以检测 DataFrame 中的缺失值,返回一个相同形状的布尔型 DataFrame。结合 sum() 方法可以统计每列的缺失值数量,结合 mean() 可以得到缺失值比例。

# 识别缺失值 print("每列缺失值数量:") print(df.isnull().sum()) print("\n每列缺失值比例:") print(df.isnull().mean().round(4) * 100)

2. 统计缺失比例

了解缺失值比例对于选择处理策略至关重要。缺失比例低于 5% 的列通常可以直接删除缺失行;缺失比例在 5%-20% 之间的列适合使用填充方法;缺失比例超过 50% 的列通常需要考虑是否删除整列。需要结合业务场景和专业领域知识来做出判断。

3. 删除缺失值

dropna() 方法可以删除包含缺失值的行或列。通过 axis 参数控制删除行(axis=0)或列(axis=1),how 参数控制是存在任何缺失就删除(how='any')还是全部缺失才删除(how='all'),thresh 参数设置非空值的最小数量阈值。

# 删除缺失值 df_clean = df.dropna() # 删除包含任何缺失值的行 df_clean = df.dropna(axis=1) # 删除包含任何缺失值的列 df_clean = df.dropna(how="all") # 仅删除全部为空的行 df_clean = df.dropna(thresh=5) # 保留至少有5个非空值的行 df_clean = df.dropna(subset=["important_col"]) # 基于特定列删除

4. 填充缺失值

当数据量有限,删除缺失值会导致信息损失时,可以采用填充策略。不同填充方法适用于不同的数据类型和场景。

均值/中位数/众数填充:这是最简单的填充方法。对于数值型数据,使用均值或中位数填充;对于类别型数据,使用众数(出现频率最高的值)填充。中位数对异常值不敏感,在数据偏态分布时表现更好。

前向/后向填充:在时间序列数据中,缺失值通常用相邻时间点的值填充。ffill()(前向填充)将上一个有效值向后传播,bfill()(后向填充)将下一个有效值向前传播。这种方法假设数据在时间上具有连续性。

插值法:interpolate() 方法使用线性插值、多项式插值或样条插值等方式估算缺失值。线性插值假设缺失值位于前后两个有效值的连线上,适用于趋势较为平滑的数据。

KNN 填充:基于 K 近邻算法的填充方法,找到与缺失样本最相似的 K 个样本,用它们的加权平均值填充缺失值。这种方法考虑了特征之间的相互关系,效果通常优于简单统计填充,但计算成本较高。

预测模型填充:将含有缺失值的列作为目标变量,其他列作为特征,训练一个预测模型来估算缺失值。回归模型用于数值型缺失值,分类模型用于类别型缺失值。这种方法在特征之间相关性较强时效果很好,但需要注意避免数据泄露。

# 填充缺失值示例 # 均值填充 df["age"].fillna(df["age"].mean(), inplace=True) # 中位数填充 df["salary"].fillna(df["salary"].median(), inplace=True) # 众数填充 df["color"].fillna(df["color"].mode()[0], inplace=True) # 前向填充 df["temperature"].fillna(method="ffill", inplace=True) # 插值法 df["value"].interpolate(method="linear", inplace=True)

5. 缺失值处理策略选择

选择合适的缺失值处理策略需要综合考虑以下因素:缺失机制(完全随机缺失、随机缺失、非随机缺失)、缺失比例、数据类型(数值型、类别型、时间序列)、数据量大小、计算资源限制和业务场景要求。一般来说,建议从简单的策略开始尝试,通过交叉验证评估不同策略对模型性能的影响,最终选择最优方案。记录缺失值处理的过程和参数也有助于后续的结果复现。

五、异常值处理

异常值是指明显偏离其他观测值的极端数据点。异常值可能由数据录入错误、传感器故障、系统异常等原因产生,也可能是真实但罕见的极端情况。不当处理异常值会严重影响模型的训练效果和预测能力。

1. 异常值识别方法

标准差法(3σ 原则):假设数据服从正态分布,将超出均值 ± 3 倍标准差范围的数据点视为异常值。这种方法简单直观,但要求数据近似正态分布。

IQR 四分位距法:计算第一四分位数(Q1)和第三四分位数(Q3),四分位距 IQR = Q3 - Q1。将小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR 的值视为异常值。这种方法对数据分布没有假设,适用范围更广。使用 3×IQR 作为阈值可以检测极端异常值。

Z-Score 方法:计算每个数据点的 Z-Score(标准化分数),Z-Score 绝对值大于 3 的数据点通常被视为异常值。Z-Score 方法同样依赖于正态分布假设,但在多变量场景中也有扩展应用(如马氏距离)。

可视化检测:箱线图可以直观地展示数据的分布特征和异常值位置;散点图有助于发现变量之间的异常模式;直方图和密度图可以帮助观察数据的整体分布形态。可视化方法是异常值检测中不可或缺的辅助工具。

# IQR 方法检测异常值 Q1 = df["value"].quantile(0.25) Q3 = df["value"].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = df[(df["value"] < lower_bound) | (df["value"] > upper_bound)] print(f"检测到 {len(outliers)} 个异常值") # Z-Score 方法 from scipy import stats z_scores = stats.zscore(df["value"]) outlier_mask = np.abs(z_scores) > 3 print(f"Z-Score 检测到 {outlier_mask.sum()} 个异常值")

2. 异常值处理方法

删除:当异常值确定是数据录入错误,且数量较少时,直接删除是最简单的处理方法。但需要注意,删除过多数据会导致信息损失和样本偏差。

截尾:将超出阈值的异常值替换为阈值本身(如上下 1% 分位数),也称为 Winsorize 处理。这种方法保留了异常值的数量信息,但消除了其极端影响。

转换:对数据进行对数转换、平方根转换或 Box-Cox 转换可以压缩数据的尺度,减弱异常值的影响。转换后的数据通常更接近正态分布,有利于后续的统计分析。

六、重复数据处理

重复数据是数据集中常见的质量问题。重复记录可能来自多个数据源的合并、同一用户的多次提交、数据采集过程中的系统错误等。重复数据会导致统计结果偏差,模型过拟合和计算资源浪费。

1. 识别重复行

duplicated() 方法返回一个布尔 Series,标记重复的行(除第一次出现外)。通过 subset 参数可以指定基于特定的列组合来判断重复,keep 参数控制保留哪个出现位置。

# 识别完全重复的行 print(f"重复行数: {df.duplicated().sum()}") # 基于特定列识别重复 print(f"基于 'email' 列的重复数: {df.duplicated(subset=['email']).sum()}") # 查看重复行 duplicates = df[df.duplicated(keep=False)] print(duplicates.sort_values(by="id"))

2. 删除重复行

drop_duplicates() 方法用于删除重复行。默认保留第一次出现的行,可以通过 keep 参数控制保留顺序(last 保留最后出现的行,False 删除所有重复行)。

# 删除完全重复的行 df_clean = df.drop_duplicates() # 基于特定列删除重复,保留最后出现的行 df_clean = df.drop_duplicates(subset=["email", "phone"], keep="last") # 删除所有重复行(不保留任何重复) df_clean = df.drop_duplicates(keep=False) print(f"删除重复后: {df_clean.shape[0]} 行")

3. 重复数据处理策略

处理重复数据时,需要区分精确重复和模糊重复。精确重复是所有列值完全相同,可以直接删除。模糊重复是指数据在语义上重复但值略有差异,如"张三"和"张 三"、地址的细微拼写差异等。模糊重复的处理更为复杂,通常需要借助字符串相似度算法(如 Levenshtein 距离、Jaccard 相似度)或专门的重复数据检测库(如 dedupe、recordlinkage)来解决。

七、数据类型转换

正确的数据类型是数据分析的基础。从原始数据源读取的数据往往存在类型不匹配的问题,例如数值被读成了字符串、日期没有被解析为 datetime 类型等。规范的数据类型转换可以提高计算效率并避免潜在的错误。

1. 数据类型转换 (astype())

astype() 方法用于显式转换 Series 的数据类型。常见的转换包括:将字符串类型的数字转换为 float 或 int,将 float 转换为 int(注意精度损失),以及将数值转换为字符串进行分类处理。

# 数据类型转换 df["price"] = df["price"].astype(float) # 字符串转浮点数 df["quantity"] = df["quantity"].astype(int) # 浮点数转整数 df["id"] = df["id"].astype(str) # 数值转字符串

2. 日期时间类型处理

Pandas 的 to_datetime() 函数可以灵活地解析各种格式的日期字符串。转换后可以使用 dt 访问器提取年、月、日、星期等时间特征,也可以进行日期运算和时间差计算。

# 日期时间转换 df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d") # 提取时间特征 df["year"] = df["date"].dt.year df["month"] = df["date"].dt.month df["day"] = df["date"].dt.day df["dayofweek"] = df["date"].dt.dayofweek # 0=周一 df["quarter"] = df["date"].dt.quarter # 日期运算 df["days_since"] = (pd.Timestamp("today") - df["date"]).dt.days

3. 类别类型 (category)

对于取值有限的离散型数据,将其转换为 category 类型可以显著节省内存并提高计算性能。category 类型适合性别、颜色、省份等枚举型数据,也支持有序类别(ordered=True)用于排序比较。

# 转换为类别类型 df["gender"] = df["gender"].astype("category") # 有序类别 size_order = ["S", "M", "L", "XL"] df["size"] = pd.Categorical(df["size"], categories=size_order, ordered=True) # 查看内存节省 print(f"内存使用: {df.memory_usage(deep=True)}")

4. 字符串处理

Pandas 的 str 访问器提供了丰富的字符串处理方法,包括大小写转换、空白去除、分割与替换、正则表达式匹配等。字符串处理在文本数据的清洗和特征工程中非常常见。

# 字符串处理 df["name"] = df["name"].str.strip() # 去除首尾空白 df["name"] = df["name"].str.lower() # 转换为小写 df["phone"] = df["phone"].str.replace("-", "") # 替换字符 df["email_domain"] = df["email"].str.split("@").str[1] # 提取域名 # 正则表达式 df["has_number"] = df["text"].str.contains(r"\d+", na=False) df["digits"] = df["code"].str.extract(r"(\d+)")

八、核心要点总结

数据采集与清洗是机器学习流程中最耗时但最重要的环节之一,通常占据整个项目 60% 以上的时间。良好的数据质量直接决定了模型效果的上限。核心要点包括:一是明确数据来源,根据场景选择公开数据集、API、爬虫或数据库等方式获取数据;二是使用 Pandas 灵活读取各种格式的文件和数据库数据,掌握 read_csv、read_excel、read_sql 等函数的参数调优;三是通过 head、info、describe 等方法全面了解数据概况,做到心中有数;四是针对缺失值,根据缺失比例和业务特点选择合适的填充策略,从简单的均值填充到复杂的预测模型填充;五是使用 IQR 或 Z-Score 方法检测异常值,结合业务判断是否删除、截尾或转换;六是检查并消除重复数据,注意模糊重复的处理;七是规范数据类型,尤其是日期时间和类别类型的正确转换。掌握这些数据预处理技术,将为后续的机器学习和数据分析工作奠定坚实的基础。