Pandas数据炼金术
Pandas 数据分析:80% 的时间在清洗,这篇帮你省掉一半
做数据分析的人都知道一句话:“垃圾进,垃圾出”。不管你的图表画得多漂亮、模型多复杂,数据本身有问题,结论就是错的。
数据科学家 80% 的时间花在数据清洗和准备上。不是算法难,是数据脏——缺失值、重复行、格式混乱、异常值、字段名不一致……
Pandas 就是干这个的。它是 Python 数据分析的基石,能帮你把脏数据变成干净、可分析的表格。
Pandas 的两个核心结构:Series 和 DataFrame
| 结构 | 维度 | 类似 |
|---|---|---|
| Series | 1维,一列数据 | Excel 里的一列 |
| DataFrame | 2维,表格 | Excel 里的整张表 |
import pandas as pd
import numpy as np
# DataFrame 就像一张表
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [25, 30, 35],
'城市': ['北京', '上海', '广州']
})
日常 99% 的工作都在跟 DataFrame 打交道——加载、清洗、筛选、聚合、输出。
拿到数据第一件事:先”体检”
假设你拿到一份销售订单数据 sales.csv,不要急着分析,先体检。
看一眼数据长什么样:
df = pd.read_csv('sales.csv')
df.head() # 前5行,看列名和样例数据
df.tail() # 后5行,看数据末尾有没有异常
df.sample(5) # 随机5行,避免排序带来的偏见
看整体概况:多少行、多少列、每列是什么类型、有没有空值
df.info()
输出里重点关注:
- Non-Null Count:小于总行数说明有缺失值
- Dtype:object 表示字符串,需要确认是否需要转成日期或数字
看数值列的统计摘要:
df.describe()
输出里关注:
- count:非空数量,进一步确认缺失
- min/max:最小值最大值,有没有明显异常(比如年龄出现 999)
- mean/std:均值和标准差,判断数据分布是否合理
专门看一下缺失值:
df.isnull().sum() # 每列缺了多少
df.isnull().sum() / len(df) * 100 # 缺失比例
拿到”体检报告”之后,你才知道数据有哪些问题:哪些列有缺失、哪些列类型不对、哪些值明显异常。先诊断再动手,别上来就瞎洗。
数据清洗:处理重复值、缺失值、异常值
重复值:先看有没有,有就删
df.duplicated().sum() # 重复行数量
df = df.drop_duplicates() # 删除重复行
# 如果某几列组合应该唯一,按这几列去重
df = df.drop_duplicates(subset=['订单号', '客户ID'])
缺失值:删还是填?
先判断严重程度:
- 某列缺失超过 50% → 考虑直接删掉这列
- 某列缺失较少 → 填充
删除:
# 删掉包含任何空值的行(谨慎用,丢数据太多)
df = df.dropna()
# 删掉"客户ID"或"订单金额"为空的行(关键字段不能丢)
df = df.dropna(subset=['客户ID', '订单金额'])
# 删掉缺失超过一半的列
threshold = len(df) * 0.5
df = df.dropna(axis=1, thresh=threshold)
填充(更常用):
# 数字列:用中位数填充(比均值更稳健,不易受异常值影响)
df['年龄'] = df['年龄'].fillna(df['年龄'].median())
# 分类列:用众数填充
df['城市'] = df['城市'].fillna(df['城市'].mode()[0])
# 时间序列:用前一个值填充(前向填充)
df['价格'] = df['价格'].fillna(method='ffill')
# 时间序列:用插值填充
df['销售额'] = df['销售额'].interpolate()
异常值:找出来,处理掉
方法一:IQR(四分位距)法——最常用
def detect_outliers(df, col):
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
return lower, upper
lower, upper = detect_outliers(df, '订单金额')
# 剔除异常值
df = df[(df['订单金额'] >= lower) & (df['订单金额'] <= upper)]
# 或把异常值"盖"到边界(盖帽法)
df['订单金额'] = df['订单金额'].clip(lower=lower, upper=upper)
方法二:业务规则过滤
# 年龄不能为负,不能超过120
df = df[df['年龄'] >= 0]
df = df[df['年龄'] <= 120]
# 单价不能为负
df = df[df['单价'] >= 0]
数据类型转换
# 字符串转数字(转不了的话变成 NaN,再处理)
df['价格'] = pd.to_numeric(df['价格'], errors='coerce')
# 字符串转日期
df['订单日期'] = pd.to_datetime(df['订单日期'])
# 转为分类类型(节省内存)
df['城市'] = df['城市'].astype('category')
数据筛选:从数据里捞出你要的那部分
布尔索引(最常用):
# 单条件
electronics = df[df['品类'] == '电子产品']
# 多条件 AND(用 &)
filtered = df[(df['年龄'] > 25) & (df['城市'] == '北京')]
# 多条件 OR(用 |)
filtered = df[(df['品类'] == '电子产品') | (df['品类'] == '图书')]
# 简化 OR:用 isin()
filtered = df[df['品类'].isin(['电子产品', '图书'])]
# 取反(NOT)
filtered = df[~df['品类'].isin(['电子产品', '图书'])]
query() 写法更接近自然语言:
filtered = df.query("年龄 > 25 and 城市 == '北京'")
filtered = df.query("品类 in ['电子产品', '图书']")
选列:
# 选一列
names = df['姓名']
# 选多列
subset = df[['姓名', '年龄', '城市']]
# 删列
df = df.drop('无用列', axis=1)
特征工程:从已有数据里”造”出新信息
算术运算创建新列
df['总金额'] = df['单价'] * df['数量']
# 条件创建
df['价格等级'] = np.where(df['单价'] > 50, '高', '低')
# 多条件
conditions = [
df['单价'] < 20,
df['单价'] < 50,
df['单价'] >= 50
]
choices = ['低档', '中档', '高档']
df['档次'] = np.select(conditions, choices)
从日期里提取信息
df['订单日期'] = pd.to_datetime(df['订单日期'])
df['年'] = df['订单日期'].dt.year
df['月'] = df['订单日期'].dt.month
df['季度'] = df['订单日期'].dt.quarter
df['星期几'] = df['订单日期'].dt.day_name() # Monday, Tuesday...
df['是否周末'] = df['订单日期'].dt.dayofweek >= 5
分箱(把连续值切成几段)
# 等距分箱
bins = [0, 18, 30, 45, 60, 100]
labels = ['<18', '18-30', '30-45', '45-60', '60+']
df['年龄段'] = pd.cut(df['年龄'], bins=bins, labels=labels)
# 等频分箱(每段样本数差不多)
df['收入等级'] = pd.qcut(df['收入'], q=4, labels=['低', '中低', '中高', '高'])
apply 自定义函数
def calc_discount(row):
if row['数量'] >= 10:
return row['单价'] * 0.9
return row['单价']
df['折后单价'] = df.apply(calc_discount, axis=1)
数据聚合:把明细变成报表
groupby 分组聚合——数据分析最核心的操作:
# 按品类分组,算总销售额
df.groupby('品类')['总金额'].sum()
# 按品类分组,算多个指标
df.groupby('品类')['总金额'].agg(['sum', 'mean', 'count', 'std'])
# 按多个维度分组
df.groupby(['品类', '城市'])['总金额'].sum()
# 不同列用不同聚合函数
df.groupby('品类').agg({
'总金额': ['sum', 'mean'],
'数量': 'sum',
'单价': 'median'
})
# 命名聚合(推荐,结果更清晰)
df.groupby('品类').agg(
总销售额=('总金额', 'sum'),
平均单价=('单价', 'mean'),
订单数=('订单号', 'count')
)
数据透视表:把分组结果展开成表格
# 行=品类,列=城市,值=总销售额
pivot = pd.pivot_table(
df,
values='总金额',
index='品类',
columns='城市',
aggfunc='sum',
fill_value=0
)
# 加总计行列
pivot = pd.pivot_table(
df,
values='总金额',
index='品类',
columns='城市',
aggfunc='sum',
margins=True,
margins_name='总计'
)
数据输出:把结果保存下来
# 保存为 CSV
df.to_csv('清洗后数据.csv', index=False, encoding='utf-8-sig')
# 保存为 Excel
df.to_excel('报表.xlsx', index=False)
# 保存多个 Sheet
with pd.ExcelWriter('报表.xlsx') as writer:
df1.to_excel(writer, sheet_name='销售明细', index=False)
df2.to_excel(writer, sheet_name='汇总', index=False)
# 保存为 JSON
df.to_json('数据.json', orient='records', force_ascii=False)