3.1 EDA 与分布诊断:先理解一行数据,再解释一张图
清洗后的表有十万行、五十列。数据平台的仪表板能在一秒内画出直方图,却不会告诉你每一行是一次任务、一次重试,还是每天的任务汇总。粒度不清,任何均值都可能把单位算错。
本课目标
- 从数据生成过程、观察单位和覆盖范围开始 EDA;
- 为数值、类别、比例和时间变量选择摘要;
- 识别直方图分箱、长尾、截断和缺失造成的假象;
- 将探索发现写成可验证假设,而不是直接下结论。
1. 先写数据说明卡
在画图前回答:
一行代表什么?
主键或重复单位是什么?
覆盖哪些时间、地区与群体?
数据如何抽样或过滤?
哪些字段在采集后计算?
缺失和迟到如何处理?
当前版本经历了哪些清洗?若同一 mission_id 因重试出现多行,事件失败数与任务失败数回答的是两个问题。若一行是每日汇总,等权均值描述平均一天,按任务数加权则描述平均一次任务。先写清单位,再按计算器。
EDA 应同时查看 raw 与 curated:raw 帮助发现采集、解析和缺失问题;curated 用于理解清洗后分析数据。只在清洗后探索,会看不到规则删除了什么。
2. 建立覆盖与粒度检查
以下代码使用 pandas 3.0 和 Matplotlib,按顺序运行。八条事件对应四个任务,后面的事件摘要不能直接标成任务摘要。
import pandas as pd
frame = pd.DataFrame({
"event_id": [f"e{i}" for i in range(8)],
"mission_id": [f"M{i // 2}" for i in range(8)],
"occurred_at": pd.to_datetime(
["2026-08-01T10:00:00Z"] * 4 + ["2026-08-03T10:00:00Z"] * 4
),
"mission_type": ["survey", "repair"] * 4,
"duration_minutes": [5, 10, 12, 20, 40, 60, 120, None],
})
overview = {
"rows": len(frame),
"missions": frame["mission_id"].nunique(),
"min_time": frame["occurred_at"].min(),
"max_time": frame["occurred_at"].max(),
"missing_event_ids": frame["event_id"].isna().sum(),
"duplicate_event_ids": frame["event_id"].dropna().duplicated().sum(),
}
print(overview)行数、实体数和事件数的比例能快速暴露粒度。再按来源、日期、schema version 和状态分组,检查某个分区是否缺失或突然激增。
3. 数值变量看中心、离散和形状
duration = frame["duration_minutes"]
summary = duration.describe(percentiles=[0.01, 0.1, 0.5, 0.9, 0.99])
summary["missing_rate"] = duration.isna().mean()
print(summary)均值适合表达总和分摊,但对长尾敏感;中位数描述典型位置,却不保留总量信息;标准差依赖均值和平方偏差,IQR 对极端值更稳健。
报告分位数时还需说明:是否包含失败记录、零值、截尾值和缺失填补值。摘要数字只有连同分析集合才可复现。
4. 直方图由分箱决定
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
duration.plot.hist(bins=20, ax=axes[0], title="20 bins")
duration.plot.hist(bins=80, ax=axes[1], title="80 bins")
for axis in axes:
axis.set_xlabel("Duration (minutes)")
plt.tight_layout()
plt.show()分箱太宽会隐藏多峰,太窄会放大随机波动。至少尝试几种宽度,并把业务边界、测量精度和样本量纳入选择。
这个小样本只有七个已观测时长,80 箱图大部分是空白。它演示分箱敏感性,不能证明存在 80 种运行状态。图中不包含缺失时长,应同时报告缺失率。
对跨数量级正值可考虑对数坐标,但要说明零和负值如何处理。对数坐标展示相对变化,不应在图注中伪装成线性尺度。
5. 长尾与截断透露生成过程
持续时间上限集中在恰好 60、120 分钟,可能来自超时规则;整数在 999 或 9999 堆积,可能是上限编码;某个时间点后精度突然改变,可能是 schema 或设备版本升级。
这些结构不应立即平滑。先按以下维度切分:
- source / device model;
- schema version;
- 时间与发布批次;
- 地区、渠道、任务类型;
- 是否被清洗或截尾。
双峰可能来自两类群体混合,也可能来自批次错误。图形提出假设,来源元数据帮助验证。
6. 类别变量同时看数量和比例
counts = frame["mission_type"].value_counts(dropna=False)
shares = frame["mission_type"].value_counts(
dropna=False, normalize=True
)
category_summary = pd.DataFrame({"count": counts, "share": shares})
print(category_summary)只看百分比会隐藏小样本,只看数量又难比较规模不同的时期。图表应同时提供分母。
高基数类别不要画上千根柱。可展示 top-N 与“其他”,但“其他”内部可能变化很大,分析表仍需保留完整类别。
未知、缺失和不适用应分开。把空值排除后重新归一化,会让已知类别比例看起来稳定,掩盖采集质量下降。
7. 比率必须回到分子分母
一行 success_rate=0.8 可能来自 4/5,也可能来自 8000/10000,两者不确定性完全不同。
EDA 中保留:
success_count
attempt_count
success_rate = success_count / attempt_count比较分组率时先画分母分布,并检查不同任务难度、曝光和观察窗口。平均各行 rate 通常不等于总成功数除以总尝试数;选择宏平均还是微平均取决于问题。
例如两组分别为 9/10 和 1/100,宏平均是 45.5%,汇总率则是 10/110,约 9.09%。两者都没算错:前者给每组相同权重,后者给每次尝试相同权重。尝试数为零的组,比率未定义,不能当作零成功率。
8. 时间序列先区分缺零与真零
某天没有行,可能是没有事件,也可能是分区没到。重采样填零前必须确认业务语义。
observed = (
frame.assign(day=frame["occurred_at"].dt.floor("D"))
.groupby(["day", "mission_type"], dropna=False)
.size().rename("observed_events").reset_index()
)
coverage = pd.MultiIndex.from_product(
[pd.date_range("2026-08-01", "2026-08-03", tz="UTC"), ["survey", "repair"]],
names=["day", "mission_type"],
).to_frame(index=False)
coverage["partition_complete"] = [True, True, True, False, True, True]
daily = coverage.merge(observed, on=["day", "mission_type"], how="left", validate="1:1")
daily["events"] = daily["observed_events"].fillna(0).astype("Int64").where(
daily["partition_complete"]
)
print(daily)事件数还应结合活跃用户、设备在线数或曝光量。总量增长可能只是覆盖面扩大,不代表单位表现改善。
覆盖清单声明 8 月 2 日 survey 分区完整,所以无行可记为零;当天 repair 分区不完整,events 保持缺失。生产中的完整性要来自接入清单,不能看事件表猜。重采样计数可能自动把空时间箱写成零,按列计数还会跳过空 ID;示例因此使用行数和显式覆盖网格。业务日若按本地时区定义,也应先转换时区再按日分桶。
9. EDA 输出是问题清单
记录格式:
观察:v3 设备的温度分布比 v2 高约 8°C
可能解释:校准差异、部署地区不同、真实硬件变化
需要数据:设备地区、校准记录、版本切换日期
验证方式:同地区同工况配对比较
当前结论:未确定,不能合并版本后直接比较把观察、解释和结论分开,可避免探索阶段把偶然模式写成事实。
常见误区
- 清洗前不能做 EDA:raw profiling 正是发现清洗需求的重要手段。
- 均值和中位数谁更真实:它们回答不同问题。
- 直方图形状是数据唯一属性:分箱与坐标会改变呈现。
- 比率可以脱离分母比较:小样本率波动更大,权重也不同。
练习
- 给一张事件表写数据说明卡,明确行粒度与重复单位。
- 用三种分箱画同一分布,记录哪些结构稳定存在。
- 构造宏平均与微平均结论相反的比率数据。
- 找出一段时间序列中的“缺日期”,判断是零事件还是缺分区。
小结
EDA 先审问数据生成过程,再审问数值。粒度、覆盖、缺失、分母和版本决定图中形状能否解释。探索的产物应是一组有证据需求的假设,不是漂亮图后的确定语气。
下一课把单变量扩展为关系图,并处理相关、混杂、分组悖论与不确定性表达。