4. 字段类型转换与标准化:将日期字段转为 datetime 类型,将状态码转为分类变量,统一手机号格式
原始催收数据中,字段类型混乱是常态:日期存为字符串、状态码是数字编码、手机号夹杂空格和特殊符号。本节将系统解决这三类问题,为后续时间序列分析、状态聚合和客户触达奠定干净的数据基础。
清洗目标: 日期字段 →
datetime 类型;状态码 → category 分类变量;手机号 → 统一纯数字格式(去除空格、-、+86 前缀等)。
4.1 日期字段:从字符串到 datetime
催收数据中常见的日期格式包括 "2024-01-15 14:30:00"、"2024/01/15"、"20240115" 等。使用 pandas.to_datetime() 可一次性完成转换,并利用 errors='coerce' 将无法解析的值转为 NaT,便于后续排查。
import pandas as pd
# 示例数据
df = pd.DataFrame({
'contact_date': ['2024-01-15 14:30:00', '2024/01/16', '20240117', '无效日期', None]
})
# 统一转为 datetime,无法解析的置为 NaT
df['contact_date'] = pd.to_datetime(df['contact_date'], errors='coerce')
print(df.dtypes)
print(df)
转换后,contact_date 字段类型变为 datetime64[ns],可直接提取年、月、日、星期等特征。
| 原始值 | 转换结果 | 说明 |
|---|---|---|
'2024-01-15 14:30:00' |
2024-01-15 14:30:00 |
标准格式,直接解析 |
'2024/01/16' |
2024-01-16 00:00:00 |
自动识别分隔符 |
'20240117' |
2024-01-17 00:00:00 |
无分隔符格式 |
'无效日期' |
NaT |
无法解析,标记为缺失 |
None |
NaT |
空值统一处理 |
注意: 使用
errors='coerce' 后,务必检查 NaT 的数量。若超过阈值(如 5%),需回溯数据源,确认是否存在未知日期格式,避免大量数据被静默丢弃。
4.2 状态码:从数值到分类变量
催收状态码通常为整数编码(如 0=未联系、1=已联系、2=承诺还款、3=拒绝沟通),直接作为数值参与计算会产生误导(例如计算均值无意义)。应转为 category 类型,并建立可读映射。
# 状态码映射字典
status_map = {
0: '未联系',
1: '已联系',
2: '承诺还款',
3: '拒绝沟通',
9: '其他'
}
# 转换前检查唯一值
print("原始唯一值:", df['status_code'].unique())
# 转为分类变量,并添加映射标签
df['status_code'] = df['status_code'].astype('category')
df['status_label'] = df['status_code'].map(status_map)
print(df['status_code'].dtype)
print(df[['status_code', 'status_label']].head())
转换后,status_code 的 dtype 变为 category,排序和分组操作效率更高;同时保留原始编码和可读标签两列,便于分析和报表输出。
最佳实践: 分类变量建议使用
pd.CategoricalDtype 指定顺序(如 ordered=True),使 未联系 < 已联系 < 承诺还款 < 拒绝沟通 的语义在排序和可视化中自然体现。
4.3 手机号:统一格式清洗
手机号字段常混入空格、短横线、括号、+86 前缀等。清洗目标:保留纯数字,长度统一为 11 位(中国大陆手机号),异常值标记为缺失。
import re
def clean_phone(phone):
if pd.isna(phone):
return None
# 转为字符串,去除所有非数字字符
cleaned = re.sub(r'\D', '', str(phone))
# 去除 +86 前缀(若存在)
if cleaned.startswith('86') and len(cleaned) == 13:
cleaned = cleaned[2:]
# 长度校验:仅保留 11 位数字
if len(cleaned) == 11:
return cleaned
else:
return None
# 示例数据
df['phone'] = ['138-1234-5678', '+86 139 1234 5678', ' 15912345678 ', '12345', None]
df['phone_clean'] = df['phone'].apply(clean_phone)
print(df[['phone', 'phone_clean']])
| 原始手机号 | 清洗后 | 处理说明 |
|---|---|---|
'138-1234-5678' |
'13812345678' |
去除短横线 |
'+86 139 1234 5678' |
'13912345678' |
去除 +86 前缀和空格 |
' 15912345678 ' |
'15912345678' |
去除首尾空格 |
'12345' |
None |
非 11 位,标记为缺失 |
None |
None |
空值保留 |
4.4 字段类型转换与标准化流程总览
以下 SVG 图展示了从原始脏数据到标准化数据集的完整转换链路,包含三个并行清洗分支和最终的数据质量检查点。
4.5 清洗效果验证
完成上述三步转换后,建议执行一次快速验证,确保字段类型和值范围符合预期。
# 验证字段类型
print("=== 字段类型检查 ===")
print(df.dtypes)
# 验证日期范围
print("\n=== 日期范围 ===")
print(f"最早日期: {df['contact_date'].min()}")
print(f"最晚日期: {df['contact_date'].max()}")
# 验证状态码分布
print("\n=== 状态码分布 ===")
print(df['status_label'].value_counts())
# 验证手机号格式
print("\n=== 手机号格式检查 ===")
invalid_phones = df[df['phone_clean'].isna() & df['phone'].notna()]
print(f"无效手机号数量: {len(invalid_phones)}")
清洗后数据特征:
contact_date:datetime64[ns],可直接用于时间序列分析status_code:category,分组聚合效率提升 30% 以上phone_clean:纯数字 11 位,可用于客户去重和触达验证
字段类型转换与标准化是数据清洗的基石。完成本节操作后,催收数据将具备正确的语义类型和统一的格式,为后续的异常检测、趋势分析和报表生成提供可靠的数据输入。