数据探索与理解:先别急着清洗,看看数据长什么样
拿到一份新数据,我个人的习惯是——先别急着写清洗代码。你想想看,连数据长什么样都不清楚,上来就咔咔一顿删空值、去重,很容易把有用的信息也干掉了。
数据探索这一步,说白了就是跟数据打个照面。看看它有多少行、多少列,每个字段是什么类型,数值分布是否合理。我见过太多人跳过这一步,结果清洗到一半才发现数据结构和自己想的完全不一样,又得回头重来。
嗯,咱们这一章就把数据探索的四个核心动作拆开揉碎了讲清楚。
2.1 数据概览:先摸清家底
数据概览就两个核心动作:看形状、看字段。我一般用 df.shape 和 df.info() 打头阵。
import pandas as pd
# 读取数据
df = pd.read_csv('sales_data.csv')
# 看行列数
print(f"数据集形状: {df.shape}")
print(f"行数: {df.shape[0]}, 列数: {df.shape[1]}")
# 看字段信息
df.info()
df.info() 会告诉你每个字段的名称、非空数量、数据类型。这一步能快速发现两个问题:
- 空值情况:哪个字段有缺失,缺了多少
- 类型异常:比如本该是日期的字段显示为 object,本该是数值的字段显示为 object
我的经验:有一次拿到一个用户行为日志,df.info() 一看,时间戳字段全是 object 类型。再一查,里面混了两种日期格式——"2024-01-15" 和 "01/15/2024"。这种问题不先发现,后面解析日期时就会报错。
2.2 描述性统计:让数字说话
概览看完了,接下来要看数值型字段的统计特征。Pandas 的 describe() 方法就是干这个的。
# 默认只统计数值型字段
df.describe()
# 如果想看所有字段(包括类别型)
df.describe(include='all')
# 只看某个字段
df['price'].describe()
describe() 会输出以下统计量:
| 统计量 | 含义 | 用途 |
|---|---|---|
| count | 非空值数量 | 判断缺失程度 |
| mean | 均值 | 数据中心趋势 |
| std | 标准差 | 数据离散程度 |
| min / max | 最小值 / 最大值 | 数据范围 |
| 25% / 50% / 75% | 四分位数 | 数据分布形态 |
一个小技巧:看均值和中位数(50%)的差距。如果两者相差很大,说明数据有偏态分布或者存在异常值。比如用户收入字段,均值 5 万但中位数只有 3 万,那大概率是少数高收入人群把均值拉高了。
2.3 数据类型识别:别让 Pandas 猜错了
Pandas 在读取数据时会自动推断字段类型,但说实话,它经常猜错。我遇到过最典型的情况:
- 身份证号被识别成 int64(其实应该是字符串)
- 金额字段带逗号或货币符号,被识别成 object
- 日期字段被识别成 object
所以,手动检查和修正数据类型是必须的。
# 查看每个字段的类型
print(df.dtypes)
# 手动转换类型
df['user_id'] = df['user_id'].astype(str)
df['price'] = pd.to_numeric(df['price'], errors='coerce')
df['date'] = pd.to_datetime(df['date'], errors='coerce')
# 类别型字段用 category 类型节省内存
df['city'] = df['city'].astype('category')
我曾经踩过的坑:有一次处理一个 500 万行的用户表,有个 "gender" 字段取值只有 "男"、"女"、"未知" 三种。我忘了转成 category 类型,结果这个字段占了 80MB 内存。转成 category 后直接降到 3MB。数据量大的时候,类型转换能省下大量内存。
2.4 数据分布分析:发现异常和规律
描述性统计只能告诉你数字,但数据长什么样,还得靠可视化。我一般用直方图和箱线图来快速判断分布情况。
import matplotlib.pyplot as plt
# 直方图:看数值分布
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
df['age'].hist(bins=30, edgecolor='black')
plt.title('年龄分布直方图')
plt.xlabel('年龄')
plt.ylabel('频数')
# 箱线图:看异常值
plt.subplot(1, 2, 2)
df.boxplot(column='age')
plt.title('年龄箱线图')
plt.ylabel('年龄')
plt.tight_layout()
plt.show()
通过分布分析,你能发现:
- 偏态分布:数据集中在一边,比如收入数据通常右偏
- 多峰分布:数据有多个聚集中心,可能代表不同群体
- 异常值:箱线图中超出上下须的点,需要重点关注
- 数据范围异常:比如年龄出现 200 岁,明显是脏数据
实际案例:我在做电商订单数据清洗时,通过直方图发现 "订单金额" 字段在 0 附近有一个异常高峰。查了一下,原来是测试订单和退款订单混进来了。这些数据如果不剔除,后续的客单价分析就会严重失真。
2.5 综合实战:一个完整的探索流程
把上面四个步骤串起来,就是一个标准的数据探索流程。我一般会写一个函数,每次拿到新数据先跑一遍。
def explore_data(df, sample_size=5):
"""
数据探索综合函数
"""
print("=" * 50)
print("1. 数据概览")
print("=" * 50)
print(f"形状: {df.shape}")
print(f"字段列表: {list(df.columns)}")
print()
print("=" * 50)
print("2. 数据类型")
print("=" * 50)
print(df.dtypes)
print()
print("=" * 50)
print("3. 缺失值统计")
print("=" * 50)
missing = df.isnull().sum()
missing_pct = (missing / len(df)) * 100
missing_df = pd.DataFrame({
'缺失数量': missing,
'缺失比例(%)': missing_pct
})
print(missing_df[missing_df['缺失数量'] > 0])
print()
print("=" * 50)
print("4. 数值型字段描述性统计")
print("=" * 50)
print(df.describe())
print()
print("=" * 50)
print(f"5. 前 {sample_size} 行数据预览")
print("=" * 50)
print(df.head(sample_size))
return None
# 使用
explore_data(df)
我的习惯:这个函数跑完后,我会把结果截图保存到项目文档里。后续做特征工程或者写报告时,随时可以回头参考。数据探索不是一次性工作,随着对业务理解的加深,你可能需要反复回来查看。
数据探索这一步,说白了就是给数据做个体检。不体检就开刀,风险太大了。你想想看,如果连数据的基本情况都不清楚,后面做的清洗、转换、建模,很可能都是在错误的基础上堆砌。
嗯,这一章的内容就到这里。记住四个核心动作:概览、统计、类型、分布。每一步都有它的价值,别跳过。