🏠 首页 攻略 CSV数据清洗全攻略:10个Python Pandas技巧处理脏数据

CSV数据清洗全攻略:10个Python Pandas技巧处理脏数据

拿到一份CSV数据,字段混乱、格式不一是常态。本文用Pandas演示10个实战清洗技巧:去重、缺失值处理、列名标准化、日期解析、类型转换、异常值过滤等,每个技巧附可运行代码。

打开一个CSV文件,第一列标题是"用户名",第二列叫"UserName",第三列又是"User Name"。

这就是数据清洗的日常。

据 Kaggle 的一项调查,数据分析师平均把 70% 的时间花在清洗数据上。真正写模型、跑分析的时间只占一小部分。

这篇文章不讲理论,直接上代码。我会演示 10 个最常用的数据清洗技巧,每个都附完整可运行代码。

第一步:导入数据,看看"脏"成什么样

假设我们有一份用户注册数据:

import pandas as pd
import numpy as np

df = pd.read_csv('users.csv')
print(df.head())
print(df.info())
print(df.isnull().sum())

运行后会发现一堆问题:

  • 有重复行
  • 有些单元格是空值
  • 日期格式不统一
  • 有些数字列存成了字符串
  • 列名大小写混乱

一个一个解决。

技巧1:删除重复行

# 删除完全重复的行
df = df.drop_duplicates()

# 如果只想基于某些列去重(比如user_id),保留第一条
df = df.drop_duplicates(subset=['user_id'], keep='first')

print(f"去重前:{len(df_before)} 行,去重后:{len(df)} 行")

技巧2:处理缺失值

三种策略,看你的数据情况选:

# 策略1:删除含缺失值的行(数据量大的时候用)
df = df.dropna()

# 策略2:用均值/中位数填充数值列
df['age'].fillna(df['age'].median(), inplace=True)

# 策略3:用众数填充分类列
df['city'].fillna(df['city'].mode()[0], inplace=True)

# 策略4:给缺失值打标签(适合缺失有含义的场景)
df['email'].fillna('no_email', inplace=True)

技巧3:统一列名

# 列名全部小写,空格替换为下划线
df.columns = df.columns.str.lower().str.replace(' ', '_')
df.columns = df.columns.str.replace('-', '_')

# 如果需要,还可以用映射表自定义列名
rename_map = {'user_name': 'username', 'signup_date': 'register_date'}
df.rename(columns=rename_map, inplace=True)

技巧4:统一日期格式

# 把多种日期格式统一为 YYYY-MM-DD
df['register_date'] = pd.to_datetime(
    df['register_date'],
    errors='coerce',  # 无法解析的转为 NaT
    infer_datetime_format=True
)

# 检查有没有解析失败的日期
bad_dates = df[df['register_date'].isna()]
print(f"有 {len(bad_dates)} 行日期格式无法解析")

# 提取日期中的年月
df['year'] = df['register_date'].dt.year
df['month'] = df['register_date'].dt.month

技巧5:统一文本大小写

# 状态列统一为大写
df['status'] = df['status'].str.upper()

# 用户名字段首字母大写
df['username'] = df['username'].str.title()

# 去掉前后空格(非常常见的问题)
for col in ['username', 'email', 'city']:
    if col in df.columns:
        df[col] = df[col].astype(str).str.strip()

技巧6:类型转换

# 金额列转数值,去掉千分位逗号和货币符号
df['amount'] = df['amount'].astype(str).str.replace('[$,]', '', regex=True)
df['amount'] = pd.to_numeric(df['amount'], errors='coerce')

# 手机号验证——只保留11位数字
df['phone'] = df['phone'].astype(str).str.extract(r'(\d{11})')

# 布尔列转换
df['is_vip'] = df['is_vip'].map({'是': True, '否': False, 'Yes': True, 'No': False})

技巧7:处理异常值

# 用 IQR(四分位距)法检测数值异常值
Q1 = df['age'].quantile(0.25)
Q3 = df['age'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

# 标记并移除异常值
df = df[(df['age'] >= lower) & (df['age'] <= upper)]

# 另一种方法:直接用分位数截断
df['age'] = df['age'].clip(df['age'].quantile(0.01), df['age'].quantile(0.99))

技巧8:拆分和合并列

# 拆分:把"张三/男/25岁"拆成三列
df[['name', 'gender', 'age_str']] = df['info'].str.split('/', expand=True)

# 合并:把分开的年月日合并成一个日期列
df['full_date'] = df['year'].astype(str) + '-' + df['month'].astype(str).str.zfill(2) + '-01'
df['full_date'] = pd.to_datetime(df['full_date'])

# 合并文本列
df['full_name'] = df['first_name'] + ' ' + df['last_name']

技巧9:枚举值标准化

# 城市名称可能有多种写法
city_mapping = {
    '北京': '北京',
    '北京市': '北京',
    '上海': '上海',
    '上海市': '上海',
    'shanghai': '上海',
    'SZ': '深圳',
    '深圳': '深圳',
}
df['city'] = df['city'].map(city_mapping).fillna(df['city'])

# 或者用模糊匹配处理(适合写法很多的情况)
from fuzzywuzzy import fuzz
def fuzzy_match(value, mapping, threshold=80):
    if value in mapping:
        return mapping[value]
    best_match = max(mapping.keys(), key=lambda k: fuzz.ratio(value.lower(), k.lower()))
    if fuzz.ratio(value.lower(), best_match.lower()) > threshold:
        return mapping[best_match]
    return value

df['city'] = df['city'].apply(fuzzy_match, mapping=city_mapping)

技巧10:保存清洗后的数据

# 保存为新的CSV,加上时间戳
from datetime import datetime
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
df.to_csv(f'users_cleaned_{timestamp}.csv', index=False, encoding='utf-8-sig')

# 保存为Parquet格式(推荐,文件更小、读取更快)
df.to_parquet(f'users_cleaned_{timestamp}.parquet', index=False)

# 备份原始数据
df_original = pd.read_csv('users.csv')
df_original.to_csv(f'users_original_backup_{timestamp}.csv', index=False)

完整的清洗流水线

把上面的技巧串起来,一个实用的清洗脚本长这样:

import pandas as pd
import numpy as np

# 1. 读取数据
df = pd.read_csv('data.csv')
print(f"原始数据:{df.shape}")

# 2. 去重
df = df.drop_duplicates()
print(f"去重后:{df.shape}")

# 3. 列名标准化
df.columns = df.columns.str.lower().str.replace(' ', '_')

# 4. 处理缺失值
df = df.dropna(subset=['user_id'])  # 核心字段不能为空
df['email'].fillna('unknown@example.com', inplace=True)

# 5. 日期统一
df['register_date'] = pd.to_datetime(df['register_date'], errors='coerce')

# 6. 文本统一
for col in ['username', 'city']:
    if col in df.columns:
        df[col] = df[col].astype(str).str.strip()

# 7. 异常值过滤
df = df[(df['age'] > 0) & (df['age'] < 120)]

# 8. 保存
df.to_csv('data_cleaned.csv', index=False, encoding='utf-8-sig')
print("清洗完成!")

几个实用建议

别急着清洗,先看看数据。 花5分钟看 df.describe()df.head(20),比直接写代码更快。

保存每一步的结果。 如果清洗过程中出了问题,可以回退。加个时间戳文件名就够用了。

用条件注释处理特殊情况。 不是每个字段都要清洗。有些脏数据本身就是有价值的信息。

CSV 文件很大怎么办? 试试分块读取:

chunk_size = 100000
for chunk in pd.read_csv('big_data.csv', chunksize=chunk_size):
    chunk = chunk.drop_duplicates()
    # 继续处理...

总结

数据清洗听起来无聊,但它是数据分析里最值得花时间做的部分。数据干净了,后续的分析和可视化才能靠谱。

上面这10个技巧覆盖了 80% 以上的日常清洗场景。剩下的20%……具体问题具体分析吧。

你遇到过最奇葩的脏数据长什么样?在评论区分享一下吧。