你拿到一份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条的技巧先看看数据全貌,然后针对性地清洗。
别试图用一套代码解决所有问题。那才是最大的坑。
你遇到过最奇葩的数据是什么样的?评论区分享一下吧。