小白python入门 - 65. 综合案例:从原始表到分析报告
1. 本课定位:是什么、为何重要
前 11 课拆开了能力;本课按真实交付节奏串成一条可复现流水线:原始导出 → 清洗 → KPI → 图/看板 →半页结论。这是数据分析岗位最常见的「周报/专题」形态。
| 概念 | 一句话 |
|---|---|
| 分析报告 | 问题 + 口径 + 证据(表/图)+ 发现 + 局限 + 建议 |
| 可复现 | 别人用同一数据与脚本得到同一数字 |
| 贯穿业务域 | 迷你电商:paid GMV、渠道、城市、日趋势 |
对比已学:单课 API 练习 vs面向决策的交付物。
2. 本质:把业务问题翻译成可计算可展示的证据链
本质:把业务问题翻译成「可计算的指标与可展示的证据链」。
| 交付件 | 必须有 |
|---|---|
| 口径说明 | 时间窗、状态过滤、金额定义 |
| 数字表 | 可对账的 KPI |
| 图 | 3~5 张关键图(趋势 + 结构) |
| 结论 | 发现 / 建议 / 下一步数据需求 |
| 局限 | 样本偏差、缺失、未含退款等 |
边界:本课不接机器学习预测;不部署在线 BI。
3. 先跑为敬:最小可跑流水线
importpandasaspdimportmatplotlib.pyplotaspltfrompathlibimportPath plt.rcParams["font.sans-serif"]=["Microsoft YaHei","SimHei"]plt.rcParams["axes.unicode_minus"]=False# --- 第1步:读原始数据 ---raw=pd.read_csv("data/orders.csv",parse_dates=["created_at"])print(f"原始行数:{len(raw)}")# --- 第2步:清洗(复用第 58 课逻辑) ---defclean_orders(raw):df=raw.copy()df["amount"]=pd.to_numeric(df["amount"],errors="coerce")df["status"]=df["status"].astype(str).str.strip().str.lower()df["channel"]=df["channel"].astype(str).str.strip().str.lower()channel_map={"小程序":"miniapp","微信":"miniapp"}df["channel"]=df["channel"].map(channel_map).fillna(df["channel"])df=df.drop_duplicates(subset=["order_id"],keep="first")df=df[df["amount"].isna()|(df["amount"]>=0)]df["is_gmv"]=df["status"].eq("paid")&df["amount"].notna()&df["amount"].gt(0)returndf clean=clean_orders(raw)Path("data").mkdir(exist_ok=True)clean.to_csv("data/orders_clean.csv",index=False)print(f"清洗后行数:{len(clean)}")# --- 第3步:算 KPI ---gmv_data=clean.loc[clean["is_gmv"]==True]total_gmv=gmv_data["amount"].sum()print(f"总 GMV:{total_gmv:.1f}元")你刚才跑了「读 → 洗 → 算」三步,这就是最简流水线。下面加出图和报告。
4. 流水线四段
load_raw → clean_orders → build_kpis → export_charts + write_report4.1 清洗(复用第 58 课)
importpandasaspdfrompathlibimportPathdefload_raw(path):returnpd.read_csv(path,parse_dates=["created_at"])defclean_orders(raw):df=raw.copy()df["amount"]=pd.to_numeric(df["amount"],errors="coerce")df["status"]=df["status"].astype(str).str.strip().str.lower()df["channel"]=df["channel"].astype(str).str.strip().str.lower()channel_map={"小程序":"miniapp","微信":"miniapp"}df["channel"]=df["channel"].map(channel_map).fillna(df["channel"])df=df.drop_duplicates(subset=["order_id"],keep="first")df=df[df["amount"].isna()|(df["amount"]>=0)]df["is_gmv"]=df["status"].eq("paid")&df["amount"].notna()&df["amount"].gt(0)returndf4.2 KPI(复用第 60~61 课)
defkpis(clean):"""算渠道 KPI + 城市 KPI + 日 GMV"""g=clean.loc[clean["is_gmv"]==True].copy()by_channel=(g.groupby("channel",as_index=False).agg(gmv=("amount","sum"),orders=("order_id","count"),uv=("user_id","nunique")).assign(aov=lambdax:x["gmv"]/x["orders"])# 客单价.sort_values("gmv",ascending=False))by_city=(g.groupby("city",as_index=False).agg(gmv=("amount","sum"),orders=("order_id","count")).sort_values("gmv",ascending=False))daily=(g.set_index("created_at").sort_index()["amount"].resample("D").sum().asfreq("D",fill_value=0).rename("gmv").reset_index())return{"by_channel":by_channel,"by_city":by_city,"daily":daily}4.3 出图(第 62~64 课任选)
importmatplotlib.pyplotaspltdefexport_charts(k,out_dir):"""导出关键图,返回文件路径列表"""paths=[]out_dir=Path(out_dir)out_dir.mkdir(parents=True,exist_ok=True)# 图1:渠道 GMV 柱状图ch=k["by_channel"]fig,ax=plt.subplots(figsize=(6,4),dpi=120)ax.bar(ch["channel"],ch["gmv"])ax.set_title("各渠道 Paid GMV")ax.set_ylabel("GMV(元)")forx,yinzip(ch["channel"],ch["gmv"]):ax.text(x,y,f"{y:.0f}",ha="center",va="bottom")p1=out_dir/"gmv_by_channel.png"fig.savefig(p1,bbox_inches="tight")plt.close(fig)paths.append(p1)# 图2:日 GMV 折线图daily=k["daily"]fig,ax=plt.subplots(figsize=(7,4),dpi=120)ax.plot(daily["created_at"],daily["gmv"],marker="o")ax.set_title("日 GMV 趋势")fig.autofmt_xdate()p2=out_dir/"gmv_daily.png"fig.savefig(p2,bbox_inches="tight")plt.close(fig)paths.append(p2)# 图3:城市 GMV 柱状图city=k["by_city"]fig,ax=plt.subplots(figsize=(6,4),dpi=120)ax.bar(city["city"],city["gmv"])ax.set_title("各城市 Paid GMV")ax.set_ylabel("GMV(元)")p3=out_dir/"gmv_by_city.png"fig.savefig(p3,bbox_inches="tight")plt.close(fig)paths.append(p3)returnpaths5. 报告结构模板
| 章节 | 写什么 |
|---|---|
| 1 背景与问题 | 本周要回答什么 |
| 2 口径 | 时间窗、paid、amount>0、去重键 |
| 3 数据质量 | 原始行数、去重、缺失、剔除比例 |
| 4 核心发现 | 3 条以内,每条绑定数字 |
| 5 图表 | 结构 + 趋势 |
| 6 建议与局限 | 可执行动作 + 数据缺口 |
口径示例(可直接贴进 report.md):
- 时间窗:2026-07-01 ~ 2026-07-31 - GMV:status=paid 且 amount>0 的 amount 之和 - 订单去重:order_id 保留首条 - 未含:退款冲减、未支付意向单6. 主函数:一键跑通
defmain():# 路径设置root=Path("day65")raw_dir=root/"data"/"raw"clean_dir=root/"data"/"clean"output_dir=root/"outputs"raw_dir.mkdir(parents=True,exist_ok=True)clean_dir.mkdir(parents=True,exist_ok=True)output_dir.mkdir(parents=True,exist_ok=True)# 复制统一数据集到 day65 目录(如无则用原始路径)src=Path("data/orders.csv")ifsrc.exists():importshutil shutil.copy2(src,raw_dir/"orders.csv")# 1. 读原始数据raw=load_raw(raw_dir/"orders.csv")# 2. 清洗clean=clean_orders(raw)assertclean["order_id"].is_unique,"order_id 不唯一!"clean.to_csv(clean_dir/"orders.csv",index=False)# 3. 算 KPIk=kpis(clean)# 4. 出图chart_paths=export_charts(k,output_dir)# 5. 写报告total_gmv=k["by_channel"]["gmv"].sum()# 防御:如果有数据才取第一条iflen(k["by_channel"])>0:top=k["by_channel"].iloc[0]top_info=f"头部渠道{top['channel']}GMV ={top['gmv']:.1f}(订单{int(top['orders'])})"else:top_info="无有效渠道数据"report=f"""# 迷你电商一周分析(示例) ## 口径 - paid 且 amount>0;order_id 去重 ## 质量 - 原始行数:{len(raw)};清洗后行数:{len(clean)}## 发现 1. 总 paid GMV ={total_gmv:.1f}元 2.{top_info}3. 详见 outputs 图:{', '.join(p.nameforpinchart_paths)}## 建议 - 对低 GMV 渠道做客单与转化专项(需补访购数据) ## 局限 - 模拟数据;未接退款与流量分母 """(root/"report.md").write_text(report,encoding="utf-8")print(report)print("done")if__name__=="__main__":main()预期输出(形态):打印总 GMV、头部渠道、done;生成report.md与三张 PNG。
代码说明——防御式编程:
if len(k["by_channel"]) > 0这行是防御式写法。如果数据为空,iloc[0]会报错。加了判断就不会崩。这也是第 58 课"除法前检查 0 除"的思路——永远假设数据可能为空。
7. 实战:你要亲手改的三处
| 任务 | 目的 |
|---|---|
| 换时间窗再跑 | 体验口径参数化 |
| 增加「城市 GMV」图 | 复用 by_city(已在 export_charts 里) |
| 用 plotly 多写一个 HTML | 复习第 64 课 |
验收清单:
- raw 未被修改
- clean 唯一 order_id
- 报告数字与
by_channel表一致 - 至少 2 张图 + 半页结论
- 他人可只跑
analyze.py复现
8. 从 Notebook 开始的指引
如果你更喜欢在 Jupyter Notebook 里做分析,推荐的结构:
day65/ notebooks/ 01_explore.ipynb ← 探索、试错 02_clean.ipynb ← 清洗 03_kpi_charts.ipynb ← KPI 和图 src/ metrics.py ← 从 notebook 抽出来的函数(可复用) data/raw/ ← 只读 outputs/ ← 图和报告Notebook → .py 的节奏:
- 在 Notebook 里探索、试代码
- 确认可行的逻辑,抽成函数写到
.py - 最终的
.py是可复现的,Notebook 是思考过程
9. 高阶技巧(点到)
| 技巧 | 说明 |
|---|---|
| 配置 YAML 存口径 | 业务改口径不改代码逻辑 |
| 数据版本 hash | 记录 CSV 校验和(hashlib.md5) |
| 从 Notebook 抽库 | src/metrics.py供多报告复用 |
| 衔接下阶段 | 同一 clean 表进阶段 E 做预测/分类 |
10. 踩坑回顾
| 坑 | 现象 | 正确做法 |
|---|---|---|
| 只有图没有口径 | 无法争论对错 | 报告首页写口径 |
| 结论写「感觉」 | 不可执行 | 每条发现对应一个数 |
| Notebook 从头点到尾无函数 | 难复用 | 抽clean/kpi/plot |
| 改 raw 文件 | 无法审计 | raw 只读 |
DataFrame 为空时iloc[0] | 报错 | 加if len(df) > 0判断 |
11. 总结复盘(阶段 D 收官)
| 项 | 内容 |
|---|---|
| 本课 | 端到端流水线 + 报告模板 + 验收清单 |
| 阶段路线回顾 | 54 工作流 → 55 NumPy → 56–60 表分析 → 61 时间 → 62–64 呈现 → 65 交付 |
| 思维拔高 | 分析的终点是决策与行动,不是 DataFrame;代码保证可复现,文字保证可执行 |
| 延伸学习 | BI 工具(可选);阶段 E 机器学习 |
| 官方入口 | pandas · matplotlib · seaborn · plotly |
阶段能力自检:
| 能力 | 自检问题 |
|---|---|
| 环境 | 能否干净 venv 跑通依赖? |
| 术语 | 能否用大白话解释 GMV/UV/AOV/口径? |
| 清洗 | 能否说明每条 drop/fillna 的业务理由? |
| 指标 | 能否用一句话定义 GMV/UV/AOV? |
| 时间 | 能否出日序列与环比? |
| 呈现 | 能否按问题选图并导出? |
| 交付 | 能否交出可复现报告? |
附录:统一数据集版
本课直接使用第 54 课生成的data/orders.csv。main()函数会自动复制到day65/data/raw/目录。如需从零开始,先运行 54 课第 9 节的gen_orders.py。