← 返回机器学习目录
← 返回学习笔记首页
数据采集与清洗
机器学习专题 · 掌握数据获取与清洗的完整技术
专题: 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 方法检测异常值,结合业务判断是否删除、截尾或转换;六是检查并消除重复数据,注意模糊重复的处理;七是规范数据类型,尤其是日期时间和类别类型的正确转换。掌握这些数据预处理技术,将为后续的机器学习和数据分析工作奠定坚实的基础。