引言
数据清洗是数据分析和机器学习项目中至关重要的一步。无论数据来自 CSV 文件、数据库还是 API,原始数据往往包含缺失值、重复项、异常值和格式不一致等问题。Python 提供了强大的库如 Pandas、NumPy 和 Scikit-learn,使得数据清洗变得高效且可扩展。本文将详细指导您如何使用 Python 进行数据清洗,从基础操作到高级技巧,并通过完整的代码示例进行说明。我们将使用一个虚构的销售数据集作为例子,该数据集包含产品名称、销售日期、销售数量、单价和客户信息。
1. 数据加载与初步探索
数据清洗的第一步是加载数据并进行初步探索。这有助于了解数据的结构、类型和潜在问题。我们将使用 Pandas 库来处理数据。
1.1 加载数据
假设我们有一个名为 sales_data.csv 的 CSV 文件,内容如下:
Product,SaleDate,Quantity,Price,Customer
Widget A,2023-01-01,10,15.5,Alice
Widget B,2023-01-02,5,20.0,Bob
Widget A,2023-01-03,12,15.5,Charlie
Widget C,2023-01-04,,25.0,David
Widget B,2023-01-05,8,20.0,Eve
Widget A,2023-01-01,10,15.5,Alice
Widget D,2023-01-06,15,30.0,Frank
使用 Pandas 加载数据:
import pandas as pd
# 加载 CSV 文件
df = pd.read_csv('sales_data.csv')
# 显示前几行
print(df.head())
输出:
Product SaleDate Quantity Price Customer
0 Widget A 2023-01-01 10.0 15.5 Alice
1 Widget B 2023-01-02 5.0 20.0 Bob
2 Widget A 2023-01-03 12.0 15.5 Charlie
3 Widget C 2023-01-04 NaN 25.0 David
4 Widget B 2023-01-05 8.0 20.0 Eve
1.2 初步探索数据
使用 info() 和 describe() 方法快速了解数据:
# 数据概览
print(df.info())
# 数值型统计
print(df.describe())
info() 输出:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7 entries, 0 to 6
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Product 7 non-null object
1 SaleDate 7 non-null object
2 Quantity 6 non-null float64
3 Price 7 non-null float64
4 Customer 7 non-null object
dtypes: float64(2), object(3)
memory usage: 368.0+ bytes
describe() 输出:
Quantity Price
count 6.000000 7.000000
mean 9.666667 21.071429
std 2.804761 5.559017
min 5.000000 15.500000
25% 8.000000 15.500000
50% 10.000000 20.000000
75% 11.000000 25.000000
max 12.000000 30.000000
从这些输出中,我们发现:
Quantity有一个缺失值(NaN)。- 数据类型:
SaleDate是对象(字符串),需要转换为日期类型。 - 有重复行(第0行和第5行相同)。
2. 处理缺失值
缺失值是数据清洗中的常见问题。Pandas 提供了多种方法来处理缺失值,如删除或填充。
2.1 识别缺失值
# 检查缺失值
print(df.isnull().sum())
输出:
Product 0
SaleDate 0
Quantity 1
Price 0
Customer 0
dtype: int64
2.2 删除缺失值
如果缺失值较少,可以直接删除:
# 删除包含缺失值的行
df_cleaned = df.dropna()
print(df_cleaned)
输出:
Product SaleDate Quantity Price Customer
0 Widget A 2023-01-01 10.0 15.5 Alice
1 Widget B 2023-01-02 5.0 20.0 Bob
2 Widget A 2023-01-03 12.0 15.5 Charlie
4 Widget B 2023-01-05 8.0 20.0 Eve
5 Widget A 2023-01-01 10.0 15.5 Alice
6 Widget D 2023-01-06 15.0 30.0 Frank
2.3 填充缺失值
如果不想删除数据,可以填充缺失值。例如,用均值填充 Quantity:
# 用均值填充
mean_quantity = df['Quantity'].mean()
df['Quantity'].fillna(mean_quantity, inplace=True)
print(df)
输出:
Product SaleDate Quantity Price Customer
0 Widget A 2023-01-01 10.0 15.5 Alice
1 Widget B 2023-01-02 5.0 20.0 Bob
2 Widget A 2023-01-03 12.0 15.5 Charlie
3 Widget C 2023-01-04 9.666667 25.0 David
4 Widget B 2023-01-05 8.0 20.0 Eve
5 Widget A 2023-01-01 10.0 15.5 Alice
6 Widget D 2023-01-06 15.0 30.0 Frank
对于分类数据,可以用众数填充:
# 假设 Customer 有缺失,用众数填充
# df['Customer'].fillna(df['Customer'].mode()[0], inplace=True)
3. 处理重复值
重复值会扭曲分析结果,需要识别并删除。
3.1 识别重复值
# 检查重复行
duplicates = df.duplicated()
print(duplicates)
输出:
0 False
1 False
2 False
3 False
4 False
5 True
6 False
dtype: bool
3.2 删除重复值
# 删除重复行
df = df.drop_duplicates()
print(df)
输出:
Product SaleDate Quantity Price Customer
0 Widget A 2023-01-01 10.0 15.5 Alice
1 Widget B 2023-01-02 5.0 20.0 Bob
2 Widget A 2023-01-03 12.0 15.5 Charlie
3 Widget C 2023-01-04 9.666667 25.0 David
4 Widget B 2023-01-05 8.0 20.0 Eve
6 Widget D 2023-01-06 15.0 30.0 Frank
如果只考虑特定列的重复,可以使用 subset 参数:
df = df.drop_duplicates(subset=['Product', 'SaleDate'])
4. 数据类型转换与格式标准化
数据类型不一致会导致计算错误。例如,日期应为 datetime 类型,字符串应统一大小写。
4.1 转换日期格式
# 转换 SaleDate 为 datetime
df['SaleDate'] = pd.to_datetime(df['SaleDate'])
print(df.dtypes)
输出:
Product object
SaleDate datetime64[ns]
Quantity float64
Price float64
Customer object
dtype: object
4.2 标准化字符串
例如,将 Product 和 Customer 转换为大写:
df['Product'] = df['Product'].str.upper()
df['Customer'] = df['Customer'].str.upper()
print(df)
输出:
Product SaleDate Quantity Price Customer
0 WIDGET A 2023-01-01 10.0 15.5 ALICE
1 WIDGET B 2023-01-02 5.0 20.0 BOB
2 WIDGET A 2023-01-03 12.0 15.5 CHARLIE
3 WIDGET C 2023-01-04 9.666667 25.0 DAVID
4 WIDGET B 2023-01-05 8.0 20.0 EVE
6 WIDGET D 2023-01-06 15.0 30.0 FRANK
4.3 处理数值范围
如果 Price 有异常值(如负数),可以过滤:
# 过滤负价格
df = df[df['Price'] > 0]
5. 处理异常值
异常值可能由于输入错误或极端事件引起。常用方法包括 IQR(四分位距)或 Z-score。
5.1 使用 IQR 检测异常值
# 计算 IQR
Q1 = df['Quantity'].quantile(0.25)
Q3 = df['Quantity'].quantile(0.75)
IQR = Q3 - Q1
# 定义异常值边界
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 识别异常值
outliers = df[(df['Quantity'] < lower_bound) | (df['Quantity'] > upper_bound)]
print(outliers)
在我们的数据中,可能没有异常值,但如果有,可以删除:
df = df[(df['Quantity'] >= lower_bound) & (df['Quantity'] <= upper_bound)]
5.2 使用 Z-score
from scipy import stats
import numpy as np
# 计算 Z-score
df['Quantity_Z'] = np.abs(stats.zscore(df['Quantity']))
# 过滤 Z-score > 3 的异常值
df = df[df['Quantity_Z'] < 3]
df = df.drop(columns=['Quantity_Z'])
6. 高级技巧:使用 Scikit-learn 进行数据清洗
对于更复杂的数据清洗,如插值或异常值检测,Scikit-learn 提供了工具。
6.1 使用 IterativeImputer 进行多重插值
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# 假设我们有多个数值列
imputer = IterativeImputer(random_state=0)
df[['Quantity', 'Price']] = imputer.fit_transform(df[['Quantity', 'Price']])
6.2 使用 IsolationForest 检测异常值
from sklearn.ensemble import IsolationForest
# 假设我们有数值特征
X = df[['Quantity', 'Price']]
iso = IsolationForest(contamination=0.1, random_state=0)
df['Anomaly'] = iso.fit_predict(X)
# 过滤异常值
df = df[df['Anomaly'] == 1]
df = df.drop(columns=['Anomaly'])
7. 数据清洗的自动化与最佳实践
7.1 创建清洗函数
为了可重复性,将清洗步骤封装成函数:
def clean_data(df):
# 删除重复值
df = df.drop_duplicates()
# 填充缺失值
df['Quantity'].fillna(df['Quantity'].mean(), inplace=True)
# 转换日期
df['SaleDate'] = pd.to_datetime(df['SaleDate'])
# 标准化字符串
df['Product'] = df['Product'].str.upper()
# 处理异常值
Q1 = df['Quantity'].quantile(0.25)
Q3 = df['Quantity'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df = df[(df['Quantity'] >= lower_bound) & (df['Quantity'] <= upper_bound)]
return df
# 应用函数
df_cleaned = clean_data(df)
print(df_cleaned)
7.2 最佳实践
- 版本控制:使用 Git 跟踪数据清洗脚本的变化。
- 日志记录:记录清洗步骤,例如使用 logging 模块。
- 测试:编写单元测试验证清洗函数,例如使用 pytest。
- 文档:注释代码,解释每个步骤的原因。
8. 结论
数据清洗是确保数据质量的关键步骤。通过 Pandas 和 Scikit-learn,我们可以高效处理缺失值、重复值、异常值和格式问题。本文从基础到高级技巧,提供了详细的代码示例。记住,数据清洗不是一次性的,而是迭代过程。根据您的数据集调整这些方法,并始终验证清洗结果。如果您有特定数据集或问题,可以进一步定制这些技术。
