课程目标
- 识别并处理数据中的缺失值、重复值和异常值。
- 掌握数据类型的转换与格式化。
- 学习数据分箱(离散化)与标准化。
核心Skills
- 数据清洗:处理缺失值、重复值、异常值
- 数据转换:类型转换、日期处理、字符串处理
- 数据加工:分箱、标准化、特征衍生
课件内容与代码
步骤1:导入库与数据
python# 代码文件:day2_data_cleaning.ipynb
# 沿用第1天处理后的数据,或直接读取
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime
# 读取第1天保存的数据
df = pd.read_csv('processed_orders_with_profit.csv', encoding='utf-8')
print(f">>> 数据加载成功!形状: {df.shape}")
display(df.head())
步骤2:深入检查数据质量
pythonprint("="*60)
print("【步骤1:数据质量深度检查】")
print("="*60)
# 1. 缺失值检查
missing_summary = df.isnull().sum()
missing_percentage = (df.isnull().sum() / len(df)) * 100
missing_df = pd.DataFrame({'缺失数量': missing_summary, '缺失比例%': missing_percentage.round(2)})
print(">>> 各字段缺失值统计:")
display(missing_df[missing_df['缺失数量'] > 0])
# 2. 重复值检查
duplicate_rows = df.duplicated().sum()
print(f">>> 完全重复的行数: {duplicate_rows}")
if duplicate_rows > 0:
display(df[df.duplicated()].head())
# 3. 关键字段唯一值检查
print("\n>>> 关键字段唯一值数量:")
print(f" 订单ID唯一数: {df['order_id'].nunique()} (应与总行数{len(df)}一致)")
print(f" 用户ID唯一数: {df['user_id'].nunique()}")
print(f" 商品ID唯一数: {df['product_id'].nunique()}")
步骤3:处理缺失值
pythonprint("="*60)
print("【步骤2:处理缺失值】")
print("="*60)
# 假设 `cost_price` 和 `brand` 可能存在缺失
# 策略1:删除缺失率过高的行(如果某列缺失太多)
# 策略2:填充 - 数值列用中位数/均值,分类列用众数或‘未知’
# 填充数值型缺失值(例如成本价)
if 'cost_price' in df.columns and df['cost_price'].isnull().sum() > 0:
median_cost = df['cost_price'].median()
df['cost_price'].fillna(median_cost, inplace=True)
print(f">>> 已用中位数 {median_cost:.2f} 填充 cost_price 缺失值。")
# 填充分类型缺失值(例如品牌)
if 'brand' in df.columns and df['brand'].isnull().sum() > 0:
mode_brand = df['brand'].mode()[0] # 众数
df['brand'].fillna(mode_brand, inplace=True)
print(f">>> 已用众数 '{mode_brand}' 填充 brand 缺失值。")
# 或者填充为‘未知’
# df['brand'].fillna('未知', inplace=True)
print(">>> 填充后缺失值复查:")
print(df.isnull().sum())
步骤4:处理重复值
pythonprint("="*60)
print("【步骤3:处理重复值】")
print("="*60)
# 删除完全重复的行
initial_count = len(df)
df.drop_duplicates(inplace=True)
final_count = len(df)
print(f">>> 删除重复行:从 {initial_count} 行减少到 {final_count} 行,删除了 {initial_count - final_count} 行。")
# 业务逻辑去重:例如,同一订单ID不应有重复(除非是不同商品,但本例中order_id若唯一)
# 检查并处理可能的业务重复
order_id_dups = df['order_id'].duplicated().sum()
if order_id_dups > 0:
print(f"警告:发现 {order_id_dups} 个重复的订单ID,需根据业务逻辑检查。")
# 可能需要按其他列聚合或删除
步骤5:处理异常值
pythonprint("="*60)
print("【步骤4:检测与处理异常值】")
print("="*60)
# 1. 使用描述性统计和箱线图识别异常值
print(">>> 数值字段描述统计 (关注min, max, 与分位数的差距):")
display(df[['quantity', 'unit_price', 'total_amount', 'gross_profit']].describe())
# 绘制箱线图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
numeric_cols = ['quantity', 'unit_price', 'total_amount', 'gross_profit']
for i, col in enumerate(numeric_cols):
ax = axes[i//2, i%2]
df.boxplot(column=col, ax=ax)
ax.set_title(f'{col} 箱线图')
plt.tight_layout()
plt.show()
# 2. 基于业务规则和统计方法处理异常值
# 方法A:分位数法(IQR)
def cap_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 将超出边界的值替换为边界值(盖帽法)
return series.clip(lower_bound, upper_bound)
print(">>> 应用IQR盖帽法处理极端异常值...")
df['total_amount_capped'] = cap_outliers_iqr(df['total_amount'])
df['gross_profit_capped'] = cap_outliers_iqr(df['gross_profit'])
# 对比处理前后
print(">>> 处理前后 total_amount 范围对比:")
print(f" 原始范围: [{df['total_amount'].min():.2f}, {df['total_amount'].max():.2f}]")
print(f" 盖帽后范围: [{df['total_amount_capped'].min():.2f}, {df['total_amount_capped'].max():.2f}]")
步骤6:数据类型转换与日期处理
pythonprint("="*60)
print("【步骤5:数据类型转换与日期处理】")
print("="*60)
# 1. 日期字段转换(如果存在)
if 'order_date' in df.columns:
df['order_date'] = pd.to_datetime(df['order_date'])
print(">>> order_date 已转换为 datetime 类型。")
# 衍生新的日期特征
df['order_year'] = df['order_date'].dt.year
df['order_month'] = df['order_date'].dt.month
df['order_day'] = df['order_date'].dt.day
df['order_weekday'] = df['order_date'].dt.weekday # 周一=0, 周日=6
print(">>> 已衍生出年、月、日、星期几等特征。")
display(df[['order_date', 'order_year', 'order_month', 'order_day', 'order_weekday']].head())
# 2. 分类字段转换为category类型以节省内存
cat_cols = ['city', 'category', 'brand', 'warehouse']
for col in cat_cols:
if col in df.columns:
df[col] = df[col].astype('category')
print(f">>> {col} 已转换为 category 类型。")
步骤7:数据分箱(离散化)
pythonprint("="*60)
print("【步骤6:数据分箱(离散化)】")
print("="*60)
# 1. 等宽分箱:将用户消费金额分为高、中、低三档
if 'total_amount_capped' in df.columns:
df['amount_level'] = pd.cut(df['total_amount_capped'],
bins=[0, 500, 2000, df['total_amount_capped'].max()],
labels=['低', '中', '高'],
include_lowest=True)
print(">>> 订单金额分箱(等宽)完成。")
print(df['amount_level'].value_counts())
# 2. 等频分箱(按分位数):将用户按消费金额分为4组,每组数量大致相等
if 'total_amount_capped' in df.columns:
df['amount_quantile'] = pd.qcut(df['total_amount_capped'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
print(">>> 订单金额等频分箱完成。")
print(df['amount_quantile'].value_counts())
步骤8:数据标准化(归一化)
pythonprint("="*60)
print("【步骤7:数据标准化】")
print("="*60)
# 为后续建模准备,对数值特征进行标准化
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# 假设我们需要标准化的列
scale_cols = ['unit_price', 'total_amount_capped', 'gross_profit_capped']
scaler = MinMaxScaler() # 归一化到[0,1]
# scaler = StandardScaler() # Z-score标准化
df_scaled = df.copy()
df_scaled[scale_cols] = scaler.fit_transform(df[scale_cols])
print(">>> 已对数值列进行归一化处理。")
display(df_scaled[scale_cols].head())
步骤9:保存清洗后的数据
python# 保存清洗和预处理后的数据
df.to_csv('cleaned_orders.csv', index=False, encoding='utf-8')
df_scaled.to_csv('cleaned_and_scaled_orders.csv', index=False, encoding='utf-8')
print(">>> 清洗后的数据已保存为 'cleaned_orders.csv'")
print(">>> 清洗并标准化后的数据已保存为 'cleaned_and_scaled_orders.csv'")
本日要点总结
- 数据质量评估:
.isnull(), .duplicated(), .nunique()。
- 缺失值处理:
.fillna() 用中位数、众数或固定值填充;.dropna() 删除。
- 异常值检测:箱线图、IQR(四分位距)法。
- 异常值处理:盖帽法(
.clip())、删除或视为特殊值。
- 类型转换:
pd.to_datetime(), .astype('category')。
- 特征衍生:从日期字段提取年、月、日等。
- 数据分箱:
pd.cut()(等宽)、pd.qcut()(等频)。
- 数据标准化:
MinMaxScaler(归一化)、StandardScaler(Z-score标准化)。