🏠 首页 攻略 Pandas 数据清洗实战:5 个高频问题一网打尽

Pandas 数据清洗实战:5 个高频问题一网打尽

分享 Pandas 数据处理中遇到的 5 个最常见清洗难题及解决方案,包含真实代码示例和性能优化技巧。适合数据分析师、Python 开发者参考学习。

数据清洗占数据分析工作流的 60%-80%。这不是夸张,是血泪经验。

我用 Pandas 处理过从几百行到几亿行的数据集,踩过不少坑。今天把最常遇到的 5 个清洗问题和解决方案整理出来,全是能直接用的代码。


一、缺失值:删还是填?怎么选?

看到 DataFrame 里有 NaN,第一反应是赶紧处理。但怎么填,是有讲究的。

先看缺失分布:

import pandas as pd

df = pd.read_csv('sales_data.csv')
print(df.isnull().sum())
print(df.isnull().mean() * 100)  # 各列缺失百分比

如果某一列缺失率超过 70%,直接删掉这一列通常是最省事的:

# 删除缺失率 > 70% 的列
high_null_cols = df.columns[df.isnull().mean() > 0.7]
df.drop(columns=high_null_cols, inplace=True)

对于数值型列,用中位数填充比均值更稳健,因为异常值会影响均值:

# 数值列 - 中位数填充
df['age'] = df['age'].fillna(df['age'].median())

# 也可以用众数(最频繁出现的值)填充分类变量
df['city'] = df['city'].fillna(df['city'].mode()[0])

如果你需要更智能的填充,比如根据其他列来预测缺失值,可以用 KNN 填充:

from sklearn.impute import KNNImputer

imputer = KNNImputer(n_neighbors=3)
numeric_cols = df.select_dtypes(include=['number']).columns
df[numeric_cols] = imputer.fit_transform(df[numeric_cols])

记住:不要盲目全用均值填充。先看看缺失是不是有规律的——比如某些地区的收入数据系统性缺失,那就值得单独分析,而不是简单掩盖。


二、重复数据:怎么识别和处理?

重复数据是分析的大敌。一条重复的记录会把平均值、总和全部扭曲。

# 查看总重复行数
print(f"重复行数: {df.duplicated.sum()}")
print(f"重复比例: {df.duplicated.sum() / len(df) * 100:.2f}%")

默认 duplicated() 只保留第一次出现的那一行,后续重复的都标记为 True。

删除重复行很简单:

# 删除所有重复行,只保留第一行
df.drop_duplicates(inplace=True)

# 也可以指定哪些列作为判断重复的依据
df.drop_duplicates(subset=['user_id', 'order_date'], keep='last', inplace=True)

注意:keep='last' 会保留最后一次出现的记录,keep=False 则把所有重复的都删掉。

有时候重复并不是完全一样,而是非常相似。比如用户名字拼写稍有不同:

# 模糊去重需要先安装 fuzzywuzzy
# from fuzzywuzzy import fuzz

def fuzzy_dedup(df, column, threshold=90):
    """基于相似度阈值进行模糊去重"""
    indices_to_keep = []
    for i, row in df.iterrows():
        is_duplicate = False
        for j in indices_to_keep:
            if fuzz.ratio(row[column], df.loc[j, column]) > threshold:
                is_duplicate = True
                break
        if not is_duplicate:
            indices_to_keep.append(i)
    return df.loc[indices_to_keep]

# df = fuzzy_dedup(df, 'company_name', threshold=85)

这个方法计算量较大,适合中小数据集。上百万行时建议先用聚类算法预筛选。


三、数据类型:整型被当成字符串了

读取 CSV 后,最容易遇到的问题是类型错误。数字被读成了字符串,日期没被正确解析。

# 检查当前类型
print(df.dtypes)

# 批量转换类型
df['price'] = pd.to_numeric(df['price'], errors='coerce')  # 转换失败变成 NaN
df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce')

# 修复价格中的符号干扰
df['price'] = df['price'].replace('[\$,]', '', regex=True).astype(float)

errors='coerce' 是关键——它会让无法转换的值变成 NaN,而不是报错崩溃。之后再用之前说的方法处理这些新生成的缺失值。

整数类型也可以优化内存占用:

# 将 int64 转为更小的整数类型(如果值在范围内)
df['category_id'] = pd.to_numeric(df['category_id'], downcast='integer')

# 同样可以优化 float
df['rating'] = pd.to_numeric(df['rating'], downcast='float')

使用 df.info(memory_usage='deep') 可以查看实际内存占用,优化后可以节省大量内存。


四、字符串清洗:空格、大小写、特殊字符

文本数据经常带着各种脏东西。空格、多余换行、大小写混用,都是常见问题。

# 去除首尾空格
df['product_name'] = df['product_name'].str.strip()

# 统一大小写
df['email'] = df['email'].str.lower()
df['country'] = df['country'].str.title()  # 每个单词首字母大写

# 替换内部多余空格
df['description'] = df['description'].str.replace(r'\s+', ' ', regex=True)

正则清洗也很常用:

# 去除所有非字母数字字符(除空格外)
df['phone'] = df['phone'].replace(r'[^\d]', '', regex=True)

# 提取邮箱用户名部分
df['username'] = df['email'].str.extract(r'(.+)@')

如果要做批量标准化,可以定义函数:

def normalize_text(text):
    if pd.isna(text):
        return text
    text = str(text).strip().lower()
    text = re.sub(r'\s+', ' ', text)
    return text

df['comment'] = df['comment'].apply(normalize_text)

记住:清洗前先备份原始数据。有些清洗操作是不可逆的,万一发现有问题,得有原始数据回滚。


五、异常值检测:哪些数据需要特别关注?

异常值不一定要删掉,但它们需要被识别和处理。不同的场景有不同的处理方式。

方法 1:Z-score 法(适用于正态分布数据)

from scipy import stats

z_scores = stats.zscore(df['sales_amount'])
abs_z_scores = abs(z_scores)

# 超出 3 个标准差的视为异常
filtered = df[abs_z_scores < 3]
print(f"异常值数量: {(abs_z_scores >= 3).sum()}")

方法 2:IQR 法(四分位距,更鲁棒)

Q1 = df['sales_amount'].quantile(0.25)
Q3 = df['sales_amount'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['sales_amount'] < lower_bound) | (df['sales_amount'] > upper_bound)]
print(f"异常值数量: {len(outliers)}")

处理方式取决于业务场景:

  • 明显输入错误(如年龄写成 200 岁)→ 修正或删除
  • 极端但合理的值(如某次促销活动销量暴增)→ 保留,但做单独分析
  • 系统错误导致的负值 → 检查数据来源,必要时过滤

六、性能提示:大数据量怎么处理?

当数据量大到内存吃紧时,上面的方法可能跑得慢或者报错。这里有几个提速技巧:

# 1. 只读取需要的列
df = pd.read_csv('large_data.csv', usecols=['id', 'date', 'amount'])

# 2. 指定数据类型,减少内存占用
dtype_map = {
    'id': 'int32',
    'date': 'object',  # 可以先存字符串,处理后转 datetime
    'amount': 'float32'
}
df = pd.read_csv('large_data.csv', dtype=dtype_map)

# 3. 分块处理
for chunk in pd.read_csv('huge_file.csv', chunksize=100000):
    # 对每个块做清洗
    chunk['amount'] = chunk['amount'].fillna(chunk['amount'].median())
    # 保存或进一步处理

# 4. 使用 category 类型降低分类变量内存消耗
df['category'] = df['category'].astype('category')

数据清洗没有银弹,每一步都需要结合业务理解来做判断。上面这 5 个问题覆盖了日常 80% 的场景,掌握它们你已经能应对大多数情况了。

你的数据清洗过程中遇到过什么头疼的问题?是怎么解决的?欢迎分享交流。