一、背景:每周重复的表格问答
你有一份销售表格,每周都有人问你 slightly 不同的问题:华北上个月卖得怎么样?只看键盘的行去掉升降桌行不行?1 月前两周的数据发我一份?你打开表格、筛一遍、算一遍、截图发过去,下一个问题又来了,同样动作再来一遍。
解法不是把 Excel 发给所有人(版本满天飞),也不是每次手算,而是把表格分析包成一个小 Web 应用:别人自己选日期、地区、产品,结果自动更新。你只维护一份 notebook,所有人看到的都是最新口径。本文用 Python、Mercury 和 MLJAR Studio,一段一段生成这个应用:一次只做一块,跑起来看效果,再做下一块。
二、原理:notebook 如何变成应用
Mercury 应用本质还是 notebook:pandas 负责读表过滤计算,Altair 负责画图,Mercury 负责把变量变成页面控件。当用户改了页面上的日期或下拉框,Mercury 用新值重跑 notebook 并刷新输出。就这么简单。
关键设计只有一句:所有展示(指标、图表、表格)都用同一个过滤后的 dataframe。当过滤器变化,所有结果一起变,口径天然一致。出问题也只查一处:过滤条件写对没有。
工作流上强烈建议逐段生成:不要让 AI 一次输出整个 dashboard。一次输出 200 行,出错了你都不知道哪段坏的。一次一段,每段跑通再继续,布局边看边调(图表放表上还是表旁、指标要不要标题),比事前描述高效得多。MLJAR Studio 侧栏的 AI 助手就是为这个设计的:针对当前 cell 提建议,你看过再决定插入位置。
三、环境准备
- 安装 MLJAR Studio(内置 Mercury,无需单独装 Mercury)。
- 下载演示数据:datasets-for-start 仓库 sales_2025 目录,取 2025-01.xlsx(503 条 1 月订单),存到本地如 ~/Documents/sales/。
-
新建 notebook,确认 imports 可用:
import pandas as pd
import altair as alt
import mercury as mr -
先用 pd.read_excel 读一遍,确认 sheet 名(本教程为 Orders)和列名(order_date、region、product、revenue、units、order_id),列名对不上后面全错,先对齐。
四、分步实战:逐段生成 dashboard
第 1 步:读 Excel 数据
file_path = "/home/piotr/Documents/sales/2025-01.xlsx" # 换成你的路径
sales = pd.read_excel(file_path, sheet_name="Orders")
print(sales.shape)
print(sales.dtypes)
print(sales["order_date"].min(), sales["order_date"].max())
先看行数、列类型、日期范围。日期列读成字符串是第一大坑,转成 datetime 再往下走:sales[“order_date”] = pd.to_datetime(sales[“order_date”])。发布前把绝对路径换成相对路径或上传文件方式,否则别人打不开。
第 2 步:加过滤器(日期加地区加产品)
min_date = sales["order_date"].min().date()
max_date = sales["order_date"].max().date()
date_filter = mr.DateRange(label="Order date", value=[min_date, max_date],
min=min_date, max=max_date)
region_filter = mr.MultiSelect(label="Region",
choices=sorted(sales["region"].dropna().unique().tolist()),
value=sorted(sales["region"].dropna().unique().tolist()))
product_filter = mr.MultiSelect(label="Product",
choices=sorted(sales["product"].dropna().unique().tolist()),
value=sorted(sales["product"].dropna().unique().tolist()))
要点:选项从表格里动态取(unique),不要手写死;默认全选,进页面先看到整月。有新地区新产品进表,过滤器自动出现。过滤器 cell 放 notebook 顶部,Mercury 按 notebook 顺序渲染,过滤器在结果之前才符合直觉。
第 3 步:用过滤器值过滤数据
start_date, end_date = pd.to_datetime(date_filter.value)
filtered_sales = sales[
(sales["order_date"] >= start_date)
& (sales["order_date"] <= end_date)
& (sales["region"].isin(region_filter.value))
& (sales["product"].isin(product_filter.value))
].copy()
从这行起,后面所有计算只用 filtered_sales,不再碰 sales。这就是口径一致的全部秘密。条件之间用与连接,全满足才保留。空结果要有兜底:if filtered_sales.empty 则提示调整筛选,不要让后面除以零。
第 4 步:头部指标 KPI
total_revenue = filtered_sales["revenue"].sum()
total_orders = filtered_sales["order_id"].nunique()
total_units = filtered_sales["units"].sum()
average_order_value = (filtered_sales.groupby("order_id")["revenue"].sum().mean()
if total_orders > 0 else 0)
mr.Markdown("## Key performance indicators")
mr.Indicator([mr.Indicator(label="Total revenue", value=f"{total_revenue:,.2f} USD"),
mr.Indicator(label="Orders", value=f"{total_orders:,}"),
mr.Indicator(label="Units sold", value=f"{total_units:,}"),
mr.Indicator(label="Average order value", value=f"{average_order_value:,.2f} USD")])
四个数:总收入、订单数、销量、客单价。客单价按订单分组后平均,不是总收入除以行数。AI 助手这时最有用:对它说 update title above indicators,它会给标题样式建议, soup 现看现选。
第 5 步:每日收入折线图(Altair)
daily = (filtered_sales.groupby(filtered_sales["order_date"].dt.date)["revenue"]
.sum().reset_index().rename(columns={"order_date": "day"}))
chart = (alt.Chart(daily).mark_line(point=True)
.encode(x="day:T", y="revenue:Q", tooltip=["day", "revenue"])
.properties(title="Daily revenue", width="container"))
chart.display() if hasattr(chart, "display") else chart
日期先对齐到天再分组,否则同一天不同时分 Sey 被拆成多组。图表和表格并排还是上下,看一眼 app 效果再定,布局决策边看边做是逐段生成的核心好处。
第 6 步:分地区汇总表
by_region = (filtered_sales.groupby("region")
.agg(revenue=("revenue", "sum"), orders=("order_id", "nunique"),
units=("units", "sum"))
.reset_index().sort_values("revenue", ascending=False))
by_region
排序让第一名在最上,表格 instantly 可读。想加一列占比:by_region[“share”] = by_region[“revenue”] / by_region[“revenue”].sum(),格式化成百分比。
第 7 步:发布成 Web 应用
notebook 跑通后,在 MLJAR Studio 里点分享或导出为 Mercury app,把数据文件随 app 一起打包(路径改相对),设好访问权限。先给一两个业务同事试用,收集过滤器够不够(常被问到的筛选没加上就是 feature 缺口),再全员推广。
五、常见坑
- 日期列是字符串:比较和分组全错位,读表后立刻 pd.to_datetime。
- 绝对路径发布:你的 /home/xxx 别人没有,发布前改相对路径或文件上传。
- 手写过滤选项:新产品进表筛选器里没有,改成从 unique 动态取。
- 后面误用 sales 原表:过滤器改了数字不动,全局搜索 sales 替换成 filtered_sales。
- 空筛选无兜底:全不选时除零报错,加 empty 判断给友好提示。
- 一次生成整个 app:200 行报错定位到哭,坚持一次一段、跑通再续。
- 列名大小写:Excel 里 Region 和代码里 region 对不上,先 print columns 对齐。
- 金额千分位:展示用格式化字符串,原值保持数字,免得后面求和变拼接。
六、总结与下一步
这套方法的复利在于:notebook 还是那个 notebook,只是外面长了一层交互皮。别人自助查数,你维护一份代码,口径永远一致。学完本篇,下一步按需扩展:合全年(merge 文件夹下所有 Excel)、理脏数据(去重、补缺失、统一品类名)、加计算列(回款率、环比)。每一块都是同样的逐段生成套路,熟练后半天就能交一个部门级小应用。
七、扩展一:合并全年 12 个月
单月跑通后,下一步是全年。文件夹里 12 个 xlsx,先合并再进同一套 dashboard。合并代码让 AI 生成时,记得提三个要求:列名不一致要报警(某个月多了列或少了列)、加一列 month 标记来源月份、合并后按各月行数对账(和单文件行数加总一致)。对账这一步最容易被 AI 省掉,必须点名要,否则某个月 sheet 名写错导致整月丢失,你都不知道。
import glob
frames = []
for f in sorted(glob.glob("/home/you/Documents/sales/2025-*.xlsx")):
df = pd.read_excel(f, sheet_name="Orders")
df["month"] = f[-8:-5] # 从文件名取月份,按实际格式调
frames.append(df)
full = pd.concat(frames, ignore_index=True)
print(full.groupby("month").size()) # 对账:每月行数
合并后日期过滤器的范围自动变成全年,KPI、图表、地区表一行不用改,这就是统一用 filtered_sales 的好处。数据量上万后 Altair 折线图会卡,记得先按天聚合再画(第 5 步已做),不要把万级散点直接扔给浏览器。
八、扩展二:脏数据清洗与计算列
业务表格一定脏:品类名大小写混杂(Keyboard 和 keyboard 并存)、缺失值、重复行、日期混着文本。上线前加一个清洗 cell,四个动作:去重(按 order_id)、品类统一(strip 加 title)、缺失值填充或丢弃并计数、日期非法行单独导出待确认。清洗前后各打印一次行数和分组计数,差值就是洗掉的东西,留档备查。AI 生成清洗代码很快,但阈值(缺失多少比例就报警)要你定,不要让它自作主张丢数据。
计算列是 dashboard 的灵魂二期:加回款率(已回款除以总额)、环比(本月比上月)、各地区占比。做法都是先 groupby 出中间表,再 merge 回来,最后格式化展示。记住展示列和计算列分离:原值保持数字,展示时才格式化成百分比字符串,否则下次求和变成字符串拼接,bug 藏得极深。
九、给业务同事的交付清单
交付不是发个链接就完事。清单四项:第一,写 5 行使用说明(筛什么、看什么、数字口径是什么,客单价定义写清楚);第二,录 2 分钟屏演示一遍,比文档管用;第三,约好数据更新节奏(每周一早上你重跑 notebook 还是定时任务自动跑);第四,留反馈入口(缺什么筛选直接提)。跑一个月,过滤器缺口和口径争议会收敛,届时这个小应用就是部门的正式数据源。每周省下的重复截图时间,就是你做这套东西的回报。
十、AI 协作话术与样式美化
和 AI 助手协作有固定话术,背下来效率翻倍。布局类:把图表移到表格右边、指标上面加二级标题、过滤器置顶。配色类:折线用蓝色系、标题加粗、表格高亮最大值行。排错类:把报错全文贴给它、说清期望(希望看到什么)、一次只改一段。每句话只提一个要求,改完运行再提下一个,一次提三个要求等于没提。AI 给的代码先读后插:看导入对不对、列名对不对、再决定放当前 cell 上面还是下面。
样式美化三板斧:标题层级(页面大标题、过滤区小标题、结果区小标题分三级);数字格式(金额千分位加货币、占比转百分比保留一位);图表标题和坐标轴中文(默认英文看着像半成品)。这三处花 20 分钟,应用观感从 demo 变产品。最后记得隐藏代码 cell 只留结果展示(Mercury 支持设置),业务同事不看代码,清爽交付。
十一、从月报到预测:dashboard 的长期生命
应用活下来之后,需求会自然生长。第二个月加环比和累计列,第三个月要各产品趋势对比,半年后问能不能预测下月销量。演进路线建议:先把手工口径全部固化成计算列(环比、占比、累计),再加参数化报表(选月份自动出月报文字总结,配 matplotlib 图),预测放最后(用 Prophet 或简单移动平均,注意预测值和实际值分开展示并标方法)。每加一块都走逐段生成:加一段、跑通、业务确认口径,再下一段。dashboard 的生命力来自口径可信,不是图表花哨,守住 filtered_sales 单一口径和清洗对账两条底线,它能陪你们团队走很远。
数据源备份别忘:Excel 原文件每月归档一份带日期的文件名,notebook 里记数据版本号,数字对不上先查版本再查代码。权限上 Mercury 支持访问控制,含薪资和成本的版本只给管理层,业务版隐藏敏感列。两套视图同一份 notebook,用参数开关控制列显隐,维护一份代码。备份加权限做到位,小应用才算真正交接完成,你休假也没人找你。
附:首周行动清单。周一读通单月表并跑出四个 KPI;周二加上三个过滤器和过滤逻辑;周三生成图表和地区表并调布局;周四合并全年数据加上清洗 cell;周五发布内测并收集筛选缺口。五天一个部门级小应用上线,之后每月加一块扩展,它会长成你们的数据门户。记住每次只动一处、跑通再续,这是整套方法唯一的心法。
一句话收束:notebook 不变,外面长交互皮;口径收敛到同一个 filtered_sales;一次一段、跑通再续。五天上线部门小应用,每月加一块扩展,备份权限口径三条底线守住,它会长成数据门户。省下的每周截图时间,就是这套方法给你发的奖金,而你维护的永远只是一份 notebook。 点击阅读原文
写在最后:今晚只干一件事——把你的真实表格读进 notebook,跑出四个 KPI 和一张图。看到数字跟着筛选一起变的那一刻,你就回不去了:再也不想手动筛表截图。剩下的过滤器、图表、发布都是顺手的事。五天后,把链接发给天天问你要数的人,世界清静了。 点击阅读原文
参考资料:MLJAR 官方教程《Turn an Excel Spreadsheet into a Web App》一文的实战流程。 点击阅读原文