

大家好,我是正在实战各种AI项目的程序员晚枫。
今天继续数据清洗——类型转换与异常值处理。
数据类型不对会导致计算错误,异常值会影响统计结果。掌握这些技巧,让你的数据质量更上一层楼。
常见数据类型问题
1 2 3 4 5 6 7 8 9 10 11
| import pandas as pd
df = pd.DataFrame({ '订单号': ['A001', 'A002', 'A003'], '金额': ['1500.50', '2300', 'invalid'], '日期': ['2024-01-15', '2024/02/20', '无效日期'], '数量': [10, -5, 1000], '类别': ['A', 'B', 'A'] })
print(df.dtypes)
|
数值类型转换
字符串转数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| df['金额_num'] = pd.to_numeric(df['金额'], errors='coerce')
df['金额_clean'] = df['金额'].str.replace(',', '').astype(float)
df['价格'] = df['价格'].str.replace('$', '').str.replace(',', '').astype(float)
numeric_cols = ['金额', '数量', '单价'] for col in numeric_cols: df[col] = pd.to_numeric(df[col], errors='coerce')
|
整数 vs 浮点数
1 2 3 4 5 6 7 8
| df['金额_int'] = df['金额'].astype(int)
df['金额_round'] = df['金额'].round().astype(int)
df['金额_Int64'] = df['金额'].astype('Int64')
|
日期时间转换
基础转换
1 2 3 4 5 6 7 8
| df['日期_dt'] = pd.to_datetime(df['日期'], errors='coerce')
df['日期_dt'] = pd.to_datetime(df['日期'], format='%Y-%m-%d', errors='coerce')
df['完整时间'] = pd.to_datetime(df[['年', '月', '日']])
|
时间戳转换
1 2 3 4 5
| df['日期'] = pd.to_datetime(df['timestamp'], unit='s')
df['日期'] = pd.to_datetime(df['timestamp'], unit='ms')
|
提取日期组件
1 2 3 4 5 6
| df['年'] = df['日期'].dt.year df['月'] = df['日期'].dt.month df['日'] = df['日期'].dt.day df['星期'] = df['日期'].dt.dayofweek df['季度'] = df['日期'].dt.quarter df['是否周末'] = df['日期'].dt.dayofweek >= 5
|
类别型数据(Category)
为什么用Category?
1 2 3 4 5 6
| print(df['类别'].memory_usage()) df['类别_cat'] = df['类别'].astype('category') print(df['类别_cat'].memory_usage())
|
有序类别
1 2 3 4 5 6 7 8 9 10 11
| from pandas.api.types import CategoricalDtype
grade_type = CategoricalDtype( categories=['C', 'B', 'A', 'S'], ordered=True ) df['等级'] = df['等级'].astype(grade_type)
df[df['等级'] > 'B']
|
异常值检测与处理
统计方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from scipy import stats import numpy as np
z_scores = np.abs(stats.zscore(df['金额'])) outliers = df[z_scores > 3]
Q1 = df['金额'].quantile(0.25) Q3 = df['金额'].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['金额'] < lower_bound) | (df['金额'] > upper_bound)] normal = df[(df['金额'] >= lower_bound) & (df['金额'] <= upper_bound)]
|
业务规则方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
df.loc[df['年龄'] < 0, '年龄'] = np.nan df.loc[df['年龄'] > 120, '年龄'] = np.nan
df.loc[~df['折扣率'].between(0, 1), '折扣率'] = np.nan
df['是否异常'] = ( (df['年龄'] < 18) | (df['消费金额'] < 0) | (df['订单日期'] > pd.Timestamp.now()) )
|
异常值处理策略
1 2 3 4 5 6 7 8 9 10 11 12 13
| df_clean = df[~df['是否异常']]
df['金额'] = df['金额'].clip(lower=0, upper=100000)
df.loc[df['是否异常'], '金额'] = df['金额'].median()
outliers_df = df[df['是否异常']].copy() print("异常订单需要人工审核:") print(outliers_df)
|
文本数据处理
清理空白字符
1 2
| df['姓名'] = df['姓名'].str.strip() df['姓名'] = df['姓名'].str.replace('\s+', ' ', regex=True)
|
统一大小写
1 2
| df['邮箱'] = df['邮箱'].str.lower() df['城市'] = df['城市'].str.title()
|
提取信息
1 2 3 4 5 6 7 8
| df['运营商'] = df['手机号'].str[:3]
df['邮箱域名'] = df['邮箱'].str.split('@').str[1]
df['入职年份'] = df['入职日期'].str[:4]
|
完整清洗流程示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| def clean_dataframe(df): """完整的数据清洗流程""" df = df.copy() df = df.drop_duplicates() df = df.dropna(how='all') numeric_cols = ['金额', '数量', '单价'] for col in numeric_cols: if col in df.columns: df[col] = pd.to_numeric(df[col], errors='coerce') if '日期' in df.columns: df['日期'] = pd.to_datetime(df['日期'], errors='coerce') if '类别' in df.columns: df['类别'] = df['类别'].astype('category') if '金额' in df.columns: df.loc[df['金额'] < 0, '金额'] = np.nan text_cols = df.select_dtypes(include=['object']).columns for col in text_cols: df[col] = df[col].str.strip() return df
df_clean = clean_dataframe(df) print("清洗完成!") print(df_clean.info())
|
性能对比:类型转换对内存的影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import pandas as pd import numpy as np
df = pd.DataFrame({ 'id': np.random.randint(1, 1000000, 1000000), 'category': np.random.choice(['A', 'B', 'C', 'D'], 1000000), 'value': np.random.randn(1000000) })
print("转换前:") print(df.memory_usage(deep=True))
df['id'] = df['id'].astype('int32')
df['category'] = df['category'].astype('category')
df['value'] = df['value'].astype('float32')
print("\n转换后:") print(df.memory_usage(deep=True))
|
进阶用法
智能类型推断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| df = df.convert_dtypes()
def optimize_dtypes(df): for col in df.columns: if df[col].dtype == 'object': try: df[col] = pd.to_datetime(df[col]) continue except: pass try: df[col] = pd.to_numeric(df[col]) continue except: pass if df[col].nunique() / len(df) < 0.5: df[col] = df[col].astype('category') elif df[col].dtype == 'int64': df[col] = pd.to_numeric(df[col], downcast='integer') elif df[col].dtype == 'float64': df[col] = pd.to_numeric(df[col], downcast='float') return df
|
异常值检测方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def detect_outliers_iqr(df, col): Q1 = df[col].quantile(0.25) Q3 = df[col].quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR return df[(df[col] < lower) | (df[col] > upper)]
from scipy import stats def detect_outliers_zscore(df, col, threshold=3): z_scores = np.abs(stats.zscore(df[col].dropna())) return df[z_scores > threshold]
def detect_outliers_percentile(df, col, lower_pct=1, upper_pct=99): lower = df[col].quantile(lower_pct / 100) upper = df[col].quantile(upper_pct / 100) return df[(df[col] < lower) | (df[col] > upper)]
|
避坑指南
❌ 坑1:混合类型列
1 2 3 4 5 6 7 8
| df = pd.DataFrame({'value': ['100', '200', 'N/A', '300']})
df['value'] = df['value'].astype(float)
df['value'] = pd.to_numeric(df['value'], errors='coerce')
|
❌ 坑2:日期格式混乱
1 2 3 4 5 6 7 8
| dates = ['2025-01-15', '01/15/2025', 'Jan 15, 2025', '20250115']
df['date'] = pd.to_datetime(df['date'], format='mixed')
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d', errors='coerce')
|
实战案例:清洗零售销售数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import pandas as pd import numpy as np
np.random.seed(42) n = 10000
df = pd.DataFrame({ 'order_id': [f'ORD{i:06d}' for i in range(n)], 'amount': np.where(np.random.rand(n) > 0.05, np.random.uniform(10, 5000, n).round(2), np.random.choice([-999, 0, 99999], n)), 'quantity': np.where(np.random.rand(n) > 0.03, np.random.randint(1, 100, n), np.random.choice([-5, 0, 999], n)), 'customer_type': np.random.choice(['VIP', 'Normal', 'vip', 'normal', 'VIP ', '正常'], n), 'date': np.random.choice(pd.date_range('2025-01-01', '2025-12-31'), n) })
type_map = {'VIP': 'VIP', 'vip': 'VIP', 'VIP ': 'VIP', 'Normal': '普通', 'normal': '普通', '正常': '普通'} df['customer_type'] = df['customer_type'].map(type_map).fillna('未知')
for col in ['amount', 'quantity']: Q1, Q3 = df[col].quantile([0.25, 0.75]) IQR = Q3 - Q1 outlier_mask = (df[col] < Q1 - 1.5*IQR) | (df[col] > Q3 + 1.5*IQR) print(f"{col}异常值: {outlier_mask.sum()}条") df.loc[outlier_mask, col] = df.loc[~outlier_mask, col].median()
df['customer_type'] = df['customer_type'].astype('category') df['date'] = pd.to_datetime(df['date'])
print(f"\n清洗后数据概览:") print(df.describe()) print(f"\n客户类型分布:") print(df['customer_type'].value_counts())
|
类型转换常见场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| df['price'] = df['price'].str.replace('¥', '').str.replace(',', '').astype(float)
df['date'] = pd.to_datetime(df['date'], format='%Y年%m月%d日')
df['rate'] = df['rate'].str.rstrip('%').astype(float) / 100
df['flag'] = df['flag'].map({'是': True, '否': False})
df['rating'] = df['rating'].str.extract(r'(\d+)').astype(int)
|
异常值处理策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|
| 删除 | 简单 | 丢数据 | 异常值少 |
| 替换为边界值 | 保留数据量 | 压缩分布 | 有明确合理范围 |
| 替换为中位数 | 鲁棒 | 改变分布 | 异常值不影响中位数 |
| 对数变换 | 压缩极端值 | 不能处理0/负值 | 右偏分布 |
| 分箱 | 简单 | 损失精度 | 模型输入 |
下节预告
下一课我们将学习数据变换-行列操作,掌握如何重塑数据结构。
👉 继续阅读:Pandas数据变换-行列操作
💬 加入学习交流群
扫码加入Python学习交流群,和数千名同学一起进步:
👉 点击加入交流群
群里不定期分享:
- 数据分析实战案例
- Python学习资料
- 求职面试经验
- 行业最新动态
推荐:AI Python数据分析实战营
🎁 限时福利:送《利用Python进行数据分析》实体书
👉 点击了解详情
课程导航
上一篇: Pandas数据清洗-处理重复值
下一篇: Pandas数据变换-行列操作
PS:类型转换是数据清洗的关键步骤。记住:先检查再转换,errors='coerce'是好帮手。
📚 推荐教材
主教材:《Excel+Python 飞速搞定数据分析与处理(图灵出品)》
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!