前面八篇画的图都是静态 PNG:好看,但它是”死”的——想知道某个点的具体数值,只能凑近了瞪眼猜。今天换一个画图库 plotly,只引入一个新概念:交互式图表。鼠标一放显示数值、滚轮缩放、点击筛选,图表从”照片”升级成”遥控器”。你的 pandas 手艺照旧全用得上。
一、真实场景:老板指着图问”这个点是多少”,你答不上来
季度汇报的静态图发出去,老板盯着折线图问:”3 月这个尖峰,具体多少?”
你低头翻 CSV,翻了半分钟找到”45.3 万”,抬头老板已经问到下一张图了。更尴尬的是散点图:”这两堆点里,哪些是华东的、哪些是华南的?”静态图上两种颜色混在一起,你只能现场打开 Excel 一列一列筛。
同事发来的图不一样:鼠标悬停到 3 月的点上,气泡直接显示”3月|销售额 45.3万|利润率 18%”;滚轮一推,图放大到 2-4 月;点击图例里的”华东”,华南的点瞬间隐藏。整场汇报他没翻过一次数据——图自己会回答问题。
这就是静态图和交互图的区别:静态 PNG 像照片,拍完就定死了;交互图表像遥控器,看图的人自己能查、能缩、能筛。今天就动手做第二种。
二、新概念:plotly —— 会”动”的图表
先说清楚格局:matplotlib(第61-68篇)没有白学,它是画图界的”标准普通话”,导出版面、打印、嵌文档都靠它。今天学的 plotly 是另一个画图库,特长只有一个——画出来的图自带交互能力。
plotly 最常用的入口是 plotly.express(习惯写成 px),它和 df.plot() 一样走”一行出图”路线。安装:
pip install plotly pandas
第一眼感受:
# first_plotly.py — 第一张会动的图
import pandas as pd
import plotly.express as px
df = pd.DataFrame({
"月份": ["1月", "2月", "3月", "4月", "5月", "6月"],
"销售额": [42, 38, 45, 50, 48, 52],
})
fig = px.line(df, x="月份", y="销售额", markers=True,
title="上半年销售趋势(万元)")
fig.write_html("trend.html")
用浏览器打开 trend.html,你会看到一张”活”的图:鼠标悬停显示数值,右上角一排小按钮(放大、平移、导出 PNG、重置视图),横轴还能拖动缩放。
三个关键点,看懂就入门了:
px.line(df, x=..., y=..., markers=True)—— 和df.plot(x=..., y=...)几乎一个句式:x横轴哪列、y纵轴哪列,markers=True让数据点带圆点(悬停目标更大更好点)。px家族还有px.bar(柱状)、px.pie(饼)、px.scatter(散点)——你学过的四种图一一对应,换个函数名而已;fig.write_html("trend.html")—— 把图保存成一个独立的网页文件。它不依赖服务器、不需要联网,发给任何人,双击就能在浏览器里打开、交互。这是今天最重要的交付动作:plotly 的图,交付格式是 HTML,不是 PNG;px.line()返回的fig叫 Figure —— 类似 matplotlib 里的ax,是”图这个对象”。fig.update_layout(title=...)、fig.update_xaxes(...)继续加工,套路和ax.set_title()一样,只是换了个名字。
一句话总结:pandas 管数据,px 管出图,write_html 管交付。图的”交互能力”是 plotly 自带的,不用你写一行交互代码。
三、动手:从 CSV 到一份”会答问题”的交互报告
第 1 步:读表,出第一张交互折线图
沿用上一篇的销售明细套路,数据先落地成 CSV:
# interactive_line.py — 读 CSV,出交互折线图
import pandas as pd
import plotly.express as px
# 造一份销售明细(真实工作里换成 pd.read_csv("你的文件.csv"))
detail = pd.DataFrame({
"月份": ["1月", "2月", "3月", "4月", "5月", "6月"] * 3,
"品类": ["数码"] * 6 + ["家居"] * 6 + ["服饰"] * 6,
"销售额": [42, 38, 45, 50, 48, 52,
25, 28, 30, 33, 31, 36,
18, 20, 22, 25, 24, 28],
})
detail.to_csv("sales.csv", index=False, encoding="utf-8-sig")
df = pd.read_csv("sales.csv")
by_month = df.groupby(["月份", "品类"], as_index=False)["销售额"].sum()
# color="品类" 一条线一个颜色,图例点击可开关
fig = px.line(by_month, x="月份", y="销售额", color="品类",
markers=True, title="上半年各品类销售趋势(万元)")
fig.write_html("trend_by_category.html")
注意 color="品类" 这个参数——它自动给每个品类一条线、配好颜色、标好图例。而且图例本身就是开关:点一下”家居”,家居的线隐藏,再点回来。这就是交互图的第一个甜头:静态图要”只看数码和家居”得重新画一张,交互图点两下就完事。
第 2 步:柱状图和饼图,换汤不换药
# interactive_bar_pie.py — 换图型,句式不变
import pandas as pd
import plotly.express as px
df = pd.read_csv("sales.csv")
by_category = df.groupby("品类", as_index=False)["销售额"].sum()
by_month = df.groupby("月份", as_index=False)["销售额"].sum()
# 柱状图:hover 自动显示每根柱子的数值
fig_bar = px.bar(by_category, x="品类", y="销售额",
color="品类", title="各品类销售额对比(万元)")
fig_bar.write_html("bar.html")
# 饼图:names=标签列,values=数值列
fig_pie = px.pie(by_category, names="品类", values="销售额",
title="各品类销售额占比")
fig_pie.write_html("pie.html")
px.bar 的参数和 px.line 一模一样;px.pie 换了两个名字——names 是”每块饼叫什么”(品类列),values 是”每块饼多大”(销售额列),这个别写反。柱状图的 hover 现在能直接告诉你”家居 213 万”,不用翻表。
第 3 步:交付 —— HTML 文件就是成品
# 顺手做个"入口页"的雏形:确认三个文件都生成了
import os
for name in ["trend_by_category.html", "bar.html", "pie.html"]:
size = os.path.getsize(name)
print(f"{name}:{size/1024:.0f} KB —— 双击即可在浏览器打开")
三个 HTML 文件,打包发给同事或挂到内网,谁都能自己查数。对比一下两种交付物:PNG 发出去,别人看到的是你替他做的结论;HTML 发出去,别人拿到的是他自己能查的数据。汇报场合,后者省掉一半答疑时间。
四、跟AI说需求:把静态脚本升级成”遥控器”
拿上一篇的 pipeline_plot.py 发给 AI:
“这是我 pandas+matplotlib 的出图脚本。帮我改成 plotly 交互版:1)三个汇总各出一张交互图(折线、柱状、饼);2)折线图 hover 显示’月份、销售额、环比增速’三项;3)三个图各存一个 HTML 文件,文件名跟图的用途对应;4)横轴月份按 1月-6月 排序,不要按拼音排。先说方案再给代码。”
它的方案里这几个点值得追问:
- 让它解释
color=和hover_data=:合格的回答会说color负责”谁一条线/一个颜色”,hover_data负责”悬停时除了 x、y 还显示哪些列”。这两个参数搞清楚,你以后想让图”显示什么”就全是配置问题,不用重画; - 追一句”环比增速哪来的”:如果它直接在 hover 里加了环比,让它把算环比的那行代码单独
print出来给你看——第 1 月没有环比,它怎么处理的?空值放在 hover 里显示成什么?这一步看懂,你才敢把数字放出去; - 让它对比”PNG 版和 HTML 版各用在什么场合”:好的回答是”打印、嵌 Word、发微信用 PNG;网页、内网、邮件附件用 HTML”。这不是技术问题,是交付常识——AI 有时会默认”全都用 HTML”,你得替它想清楚受众。
五、四个必踩的坑(先打疫苗)
fig.show()跑完”没反应”,以为代码错了。 在普通电脑上fig.show()会自动弹浏览器;但在远程服务器、无界面的环境里,它其实生成了图却没地方显示,终端一片安静。疫苗:交付永远用write_html()落文件,show()只是你自己本地预览用的。文件生成了、双击能打开,才算成功。
- hover 气泡里一堆没用的列,关键数字淹在里面。
px默认把数据行的所有列都塞进悬停气泡,列名还是”销售额_万元_sum”这种自动拼出来的长名字,看得人眼花。疫苗:显式指定hover_data=和labels=——labels={"销售额": "销售额(万元)"}把列名翻译成人话,hover_data={"月份": True, "销售额": ":.1f"}控制显示什么、怎么格式化。图是给人看的,气泡也是。
- HTML 图发到微信/公众号,对方打不开。 微信聊天里 HTML 文件会被拦截或改名,公众号正文更是只认图片。疫苗:HTML 只走”浏览器能打开”的渠道(内网、邮件附件、网盘链接);发微信和公众号时,用 plotly 右上角自带的”相机”按钮导出 PNG,或者本地截图。同一个图两种格式,按渠道分发,别硬塞。
- 饼图
names和values写反,饼画出来全是错的。 写成px.pie(df, names="销售额", values="品类")不一定报错,但饼的标签变成数字、大小变成文字,图看起来”能出图但故事不对”。疫苗:记住一句话”names 给名字,values 给数值”;画完必须悬停检查每块饼的数值和你groupby算出来的对不对得上。数据对不上,图再漂亮也是错的。
六、跟AI协作的正确姿势(交互图表特供版)
- 给 AI 的数据要带”前几行样例”。 让它写
hover_data、labels这类精细配置时,把 CSV 的head()发给它(print(df.head())的输出贴进对话),它才知道你的列名是中文还是拼音、数值是整数还是小数。凭空猜列名的代码,复制过来第一行就报 KeyError。
- 要求它”每个参数加一句注释”。 plotly 的参数又多又短(
hover_data、labels、barmode、category_orders),第一次见容易晕。让它每行参数后面加一句中文注释,你读代码就是在学参数表——下次自己改,不用再问。
- 让它当”交付检查员”。 要求它最后自查三件事:文件是否真的生成(
os.path.exists)、月份顺序是否正确、hover 数值和groupby汇总是否一致。交互图的坑全在”看起来能动,但动出来的数不对”——先核数,再炫技。
七、今日小练习
练习1:给你的数据做一张”遥控器”。 找一份你自己的 CSV(或用文中的 sales.csv),用 px.line 画交互折线图并 write_html 保存,浏览器打开后试试悬停、缩放、点图例开关(提示:fig = px.line(df, x="月份", y="销售额", color="品类", markers=True);保存用 fig.write_html("trend.html");color 分色后图例可以点击开关)。
练习2:把 hover 改成”人话”。 让悬停气泡只显示三项:月份、销售额(保留 1 位小数)、品类,并且列名显示成中文友好名(提示:fig.update_traces(hovertemplate="月份:%{x} 或者用
销售额:%{y:.1f}万
品类:%{fullData.name}")labels={"销售额": "销售额(万元)"} + hover_data 组合;对比改前改后的气泡,体会”图给谁看,气泡就写给谁”)。
练习3:一图三格式分发。 把练习1的图分别导出成 HTML 和 PNG(HTML 用 write_html,PNG 用 fig.write_image("trend.png"),需要先 pip install kaleido),想想哪个发微信、哪个挂内网(提示:微信发 PNG,浏览器渠道发 HTML;如果 write_image 报错装不上 kaleido,就用 plotly 图表右上角的相机按钮手动导出——工具不顺手时,手动路线也是路线)。
下篇预告
下一篇(第70篇):自动化报表 — 数据+图表+导出,一个程序出完整份月报。 今天 HTML 是一张图一个文件,老板要的却是”一份报告里有数据表、有三张图、有结论”。下一篇把 pandas 的数据、plotly 的图、HTML 的排版串成一条流水线:每月数据一到,跑一次程序,整份月报自动生成——你从”做报表的人”变成”管报表流水线的人”。只学一个新概念:多图+文字拼成一个完整 HTML 报告。