从零开始的数据分析之旅
业界有句名言:数据科学家80%的时间花在数据清洗上。现实中的数据永远不是干净的——缺失值、重复行、错误类型、异常值、格式混乱……掌握数据清洗技能,是从"会写代码"到"能做分析"的关键跨越。
import pandas as pd
import numpy as np
# 创建含缺失值的数据
df = pd.DataFrame({
'姓名': ['张三', '李四', None, '赵六', '张三'],
'年龄': [22, 25, np.nan, 22, 22],
'成绩': [85, 92, 78, np.nan, 85]
})
# 检查缺失值
print(df.isnull().sum()) # 每列缺失数
print(df.isnull().mean()) # 缺失比例
# 处理方式1: 删除
df_clean = df.dropna() # 删除任何含缺失值的行
df_clean = df.dropna(subset=['姓名']) # 只看姓名列
# 处理方式2: 填充
df['年龄'] = df['年龄'].fillna(df['年龄'].mean()) # 均值填充
df['成绩'] = df['成绩'].fillna(df['成绩'].median()) # 中位数填充
# 检查重复
df.duplicated() # 返回布尔Series
df.duplicated().sum() # 重复行数
# 去重
df = df.drop_duplicates() # 完全重复的行
df = df.drop_duplicates(subset=['姓名']) # 按姓名去重
df = df.drop_duplicates(keep='last') # 保留最后一个
# 数值类型
df['年龄'] = df['年龄'].astype(int)
# 日期类型
df['日期'] = pd.to_datetime(df['日期'], format='mixed', dayfirst=True)
# 分类类型(节省内存)
df['城市'] = df['城市'].astype('category')
# 字符串操作
names = names.str.strip() # 去除首尾空格
names = names.str.replace(r'\s+', '', regex=True) # 去除所有空格
names = names.str.lower() # 转小写
# IQR异常值检测
Q1 = df['值'].quantile(0.25)
Q3 = df['值'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df['值'] < lower) | (df['值'] > upper)]
clean = df[(df['值'] >= lower) & (df['值'] <= upper)]
from scipy import stats
z_scores = stats.zscore(df['值'])
outliers = df[np.abs(z_scores) > 3]
import pandas as pd
def quality_report(df):
report = pd.DataFrame({
'列名': df.columns,
'数据类型': df.dtypes.values,
'缺失数': df.isnull().sum().values,
'缺失率': (df.isnull().mean()*100).round(2).values,
'唯一值': df.nunique().values,
})
return report
df = pd.DataFrame({
'电话': ['138-1234-5678', '13812345678'],
'金额': ['1,234.56', '2,345.67']
})
# 电话号码统一格式
df['电话_clean'] = df['电话'].str.replace(r'[-\s]', '', regex=True)
# 金额数值化
df['金额_num'] = (df['金额'].str.replace(',', '').astype(float))
#!/usr/bin/env python3
# 数据清洗 — 完整实战
import pandas as pd
import numpy as np
# ============ 脏数据 ============
df = pd.DataFrame({
'姓名': ['张三', '李四', None, '赵六', '张三', '孙七', '周八'],
'年龄': [22, 25, np.nan, 22, 22, 30, np.nan],
'成绩': [85, 92, 78, np.nan, 85, 88, 95],
'城市': ['北京', '上海', '广州', '北京', '北京', '深圳', '杭州'],
'日期': ['2024-01-15', '2024/02/20', '2024-03-10',
'15-01-2024', '2024-01-15', '2024-04-05', '2024-05-20']
})
print(f"原始数据:\n{df}")
print(f"\n缺失值:\n{df.isnull().sum()}")
# ============ 缺失值处理 ============
df_clean = df.copy()
df_clean['年龄'] = df_clean['年龄'].fillna(df_clean['年龄'].mean())
df_clean['成绩'] = df_clean['成绩'].fillna(df_clean['成绩'].median())
df_clean = df_clean.dropna(subset=['姓名'])
print(f"\n清洗后缺失值:\n{df_clean.isnull().sum()}")
# ============ 去重 ============
before = len(df_clean)
df_clean = df_clean.drop_duplicates()
after = len(df_clean)
print(f"\n去重: {before} → {after}")
# ============ 类型转换 ============
df_clean['年龄'] = df_clean['年龄'].astype(int)
df_clean['日期'] = pd.to_datetime(df_clean['日期'], format='mixed', dayfirst=True)
print(f"\n数据类型:\n{df_clean.dtypes}")
# ============ 异常值检测 ============
data = pd.DataFrame({'值': [10,12,11,13,100,12,11,14,12,-50]})
Q1, Q3 = data['值'].quantile(0.25), data['值'].quantile(0.75)
IQR = Q3 - Q1
outliers = data[(data['值'] < Q1-1.5*IQR) | (data['值'] > Q3+1.5*IQR)]
print(f"\nIQR异常值: {list(outliers['值'])}")
# ============ 字符串清洗 ============
names = pd.Series([' 张三 ', '李四', '王 五', ' 赵六 '])
cleaned = names.str.strip().str.replace(r'\s+', '', regex=True)
print(f"\n字符串清洗: {list(cleaned)}")
print("\n✅ Python验证通过 — 缺失值/重复/类型转换")
| 策略 | 方法 | 适用场景 | 风险 |
|---|---|---|---|
| 删除 | dropna() | 缺失率低(<5%) | 信息损失 |
| 均值填充 | fillna(mean) | 正态分布数据 | 方差低估 |
| 中位数填充 | fillna(median) | 偏态分布 | 方差低估 |
| 众数填充 | fillna(mode) | 分类变量 | 分布偏移 |
| 前向填充 | ffill() | 时间序列 | 趋势延续 |
| 插值法 | interpolate() | 有序数据 | 过拟合 |
| 模型预测 | sklearn imputer | 多特征相关 | 计算成本 |
isnull().sum()和duplicated()是第一步# 数据清洗一页速查
# 1. 诊断
df.isnull().sum() / len(df) # 缺失率
df.duplicated().sum() # 重复数
df.dtypes # 类型
df.describe() # 统计
# 2. 处理
df = df.dropna(subset=['关键列'])
df['列'] = df['列'].fillna(df['列'].median())
df = df.drop_duplicates()
df['列'] = df['列'].astype(int)
df['日期'] = pd.to_datetime(df['日期'])
# 3. 验证
df.isnull().sum().sum() == 0 # 无缺失
len(df) == len(df.drop_duplicates()) # 无重复