引言

数据清洗是数据分析和机器学习项目中至关重要的一步。无论数据来自 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,我们可以高效处理缺失值、重复值、异常值和格式问题。本文从基础到高级技巧,提供了详细的代码示例。记住,数据清洗不是一次性的,而是迭代过程。根据您的数据集调整这些方法,并始终验证清洗结果。如果您有特定数据集或问题,可以进一步定制这些技术。