🏠 首页 攻略 Python数据处理实战:用Pandas清洗脏数据的10个技巧

Python数据处理实战:用Pandas清洗脏数据的10个技巧

面对一堆缺失值、重复行和格式混乱的数据,别慌。本文用真实案例演示10个Pandas数据清洗技巧,附完整代码,直接复制就能用。

你拿到一份Excel表格,打开一看——

日期格式乱七八糟。有的写"2024-01-15",有的写"1/15/2024",还有的干脆是"一月十五号"。

姓名列里混着空格、大小写不一致。价格列居然有负数。

别急着骂人。这种情况太常见了。

数据清洗占数据分析工作的60%以上时间。但用对了方法,这些工作可以大幅压缩。

下面给你10个最实用的Pandas数据清洗技巧,每个都带可直接运行的代码。

准备数据

先造一份"脏数据"来演示:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Alice', ' bob ', 'Charlie', 'alice', None, ' David '],
    'age': [25, np.nan, 30, 25, 35, 28],
    'date': ['2024-01-15', '01/20/2024', '2024-02-01', '2024-01-15', None, '2024-03-10'],
    'price': [100, -50, 200, 100, 150, np.nan],
    'city': ['Beijing', 'Shanghai', 'Beijing', 'beijing', 'Guangzhou', 'Shanghai']
})

技巧一:统一字符串格式

名字有空格、大小写不统一?一行搞定:

df['name'] = df['name'].str.strip().str.title()

.strip()去掉首尾空格,.title()统一首字母大写。

结果:' bob ''Bob'

技巧二:处理缺失值

有三种策略可选:

删掉含缺失值的行:

df = df.dropna(subset=['name'])

用中位数填补缺失数值:

df['age'] = df['age'].fillna(df['age'].median())

用前一个有效值填充(时间序列常用):

df['price'] = df['price'].ffill()

选哪种取决于你的业务场景。删数据会丢失信息,填数据可能引入偏差。没有绝对正确的方法。

技巧三:去除重复行

df = df.drop_duplicates()

简单粗暴。但注意,有时候"重复"是有条件的。比如两个人同名同姓但年龄不同,不应该算重复。这时候用subset参数:

df = df.drop_duplicates(subset=['name', 'age'])

技巧四:修正异常值

价格出现负数?这明显不合理。

# 方法1:直接设为0
df.loc[df['price'] < 0, 'price'] = 0

# 方法2:用分位数截断
q1, q3 = df['price'].quantile([0.05, 0.95])
df['price'] = df['price'].clip(q1, q3)

方法1适合少量异常值,方法2更稳健,不会完全丢弃极端值。

技巧五:统一日期格式

这是最头疼的问题。多种日期格式混在一起:

df['date'] = pd.to_datetime(df['date'], format='mixed')

format='mixed'是Pandas 2.0新增的功能,自动识别多种日期格式。如果报错,可以逐个指定:

def parse_date(val):
    if pd.isna(val):
        return pd.NaT
    try:
        return pd.to_datetime(val, format='%Y-%m-%d')
    except:
        try:
            return pd.to_datetime(val, format='%m/%d/%Y')
        except:
            return pd.NaT

df['date'] = df['date'].apply(parse_date)

技巧六:标准化分类变量

城市名大小写不一致,‘Beijing’和’beijing’被当成两个城市:

df['city'] = df['city'].str.lower().str.title()

或者用映射字典做精确替换:

city_map = {'shanghai': '上海', 'beijing': '北京', 'guangzhou': '广州'}
df['city_cn'] = df['city'].map(city_map)

技巧七:拆分组合列

有时候数据挤在一个单元格里,比如"张三-北京":

# 拆分成两列
df[['name', 'city']] = df['full_info'].str.split('-', expand=True)

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

技巧八:数据类型优化

读取CSV后,Pandas默认会把数字列读成float64。如果确定是整数,改成int32能省一半内存:

df['age'] = df['age'].astype('Int32')  # 注意大写的I,支持空值
df['price'] = df['price'].astype('float32')

对于分类列,用category类型:

df['city'] = df['city'].astype('category')

技巧九:透视表做初步探索

清洗之前,先了解数据长什么样:

# 按城市统计平均价格
df.groupby('city')['price'].agg(['mean', 'median', 'count'])

# 交叉表看分布
pd.crosstab(df['city'], df['age'].between(25, 30))

这些统计结果能帮你发现更多问题。比如某个城市的平均价格特别低,可能是数据录入错误。

技巧十:链式操作保持代码整洁

上面的步骤可以写成流畅的链式调用:

clean_df = (df
    .assign(name=lambda x: x['name'].str.strip().str.title())
    .dropna(subset=['name'])
    .drop_duplicates()
    .assign(price=lambda x: x['price'].clip(0, None))
    .assign(date=lambda x: pd.to_datetime(x['date'], format='mixed'))
    .assign(city=lambda x: x['city'].str.lower().str.title())
    .astype({'age': 'Int32', 'price': 'float32'})
)

看起来很长,但逻辑清晰。每一步只做一件事。

完整的清洗流程模板

把上面的技巧串起来,就是一个通用的数据清洗模板:

def clean_data(df):
    """通用数据清洗函数"""
    # 1. 字符串标准化
    for col in df.select_dtypes(include='object').columns:
        df[col] = df[col].str.strip()
    
    # 2. 处理缺失值
    df = df.dropna(subset=['关键列名'])
    
    # 3. 去重
    df = df.drop_duplicates()
    
    # 4. 异常值处理
    for col in df.select_dtypes(include='number').columns:
        q1, q3 = df[col].quantile([0.01, 0.99])
        df[col] = df[col].clip(q1, q3)
    
    # 5. 日期解析
    for col in df.columns:
        if 'date' in col.lower():
            df[col] = pd.to_datetime(df[col], errors='coerce')
    
    # 6. 类型优化
    df = df.convert_dtypes()
    
    return df

把这个函数存到你的工具库里,以后每次遇到脏数据直接调用。

最后说一句

数据清洗没有银弹。每个数据集都有自己的"脏法"。

最好的方法是:先探索,再动手。用上面第9条的技巧先看看数据全貌,然后针对性地清洗。

别试图用一套代码解决所有问题。那才是最大的坑。

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