找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2121

积分

0

好友

297

主题
发表于 昨天 06:54 | 查看: 4| 回复: 0

那天晚上快十二点,我还在公司楼下便利店门口吹风,手机一边刷着群消息,一边远程连着服务器调一个报表的图。产品在群里说了一句:“这个折线图能不能鼠标移上去看具体数值啊?最好能筛选、能放大,还能导出给老板点一点就能看。” 我当时心里一咯噔:完了,这要是纯 matplotlib,基本就是导个 PNG 甩过去,交互这事儿直接原地去世。

后来我就想起之前瞄过两眼的 Plotly,回去一试,哎,还真就一条线救命了。

你先别管 Plotly 有多强,先看它跑起来到底麻不麻烦。我那天是在 Jupyter 里随手敲了这么一段:

import pandas as pd
import plotly.express as px

# 模拟一点数据:每天的 PV 和 UV
df = pd.DataFrame({
    "date": pd.date_range("2024-01-01", periods=7),
    "pv": [100, 230, 180, 260, 300, 280, 350],
    "uv": [80, 160, 130, 210, 240, 220, 260],
})

fig = px.line(
    df,
    x="date",
    y=["pv", "uv"],
    markers=True,
    title="一周流量趋势"
)
fig.show()

这段跑完你能得到什么效果呢:一个网页里的交互图——支持拖拽缩放、鼠标悬浮看具体数值、右上角自带导出 PNG 按钮、还能在图例上点一下某条线把它隐藏。

对比一下以前 matplotlib 的玩法:同样的效果你要自己写一大坨配置,还只能导出静态图,发给老板以后老板一句“我想再看某一天的细节”,你只能重新画一张图再发一次;Plotly 这边直接让他自己框选放大看完事。

那一刻我真有点后悔用得太晚了。

Plotly 的两套“人格”:傻瓜相机 vs 单反

用久了你会发现,Plotly 大概有两种写法:

一种是上面这种 plotly.express,很像“傻瓜相机”:给它一张 DataFrame,说好 x 和 y 是哪几列,它就帮你把图堆出来,配色、图例、交互都给你安排好。它极大地简化了在 Python 中创建常见图表类型的流程。

还有一种是 plotly.graph_objects,这个就偏“单反相机”了,所有东西都能自定义,适合那种领导“乱点将军”的复杂需求。

我给你对比一下,同样是一张散点图。先是 express 版本,很轻松的那种:

import plotly.express as px

iris = px.data.iris()  # 自带的示例数据集

fig = px.scatter(
    iris,
    x="sepal_width",
    y="sepal_length",
    color="species",
    size="petal_length",
    title="鸢尾花数据散点图"
)
fig.show()

你只要看得懂“宽度”“长度”“按物种分颜色”,基本就知道图画的啥。

如果换成 graph_objects,大概这样:

import plotly.graph_objects as go
import plotly.express as px

iris = px.data.iris()

fig = go.Figure()

for species, sub_df in iris.groupby("species"):
    fig.add_trace(
        go.Scatter(
            x=sub_df["sepal_width"],
            y=sub_df["sepal_length"],
            mode="markers",
            name=species,
            marker=dict(
                size=sub_df["petal_length"] * 3,  # 大小手动算一算
                opacity=0.8
            )
        )
    )

fig.update_layout(
    title="鸢尾花数据散点图(自定义版)",
    xaxis_title="sepal_width",
    yaxis_title="sepal_length"
)

fig.show()

你看逻辑就清晰很多了:先 new 一个 Figure,然后一个物种一条 trace,最后再统一 update 布局,轴标题啊透明度啊都自己掌控。缺点就是字多点,好处是你能把图从里到外拆开改。

我自己的习惯是:简单报表、临时分析用 px.*,一旦要做“准正式的看板”、嵌入到系统里就切到 graph_objects

一个真实业务场景:活动数据监控看板

之前给运营做一个活动数据监控,需求很典型:要看每天每个渠道的转化漏斗,而且希望“点某个渠道,下面所有图跟着联动变”。这本质上是一个简单的 大数据 监控场景。

我第一版用的是最粗暴的方式:一张大表,写 SQL 把数据拉下来,再用 Plotly 做几个图拼一块:

  • 上面一行:总体 PV/UV/支付数折线图
  • 中间一行:不同渠道的条形图
  • 下面:转化漏斗

代码大概这样起手:

import pandas as pd
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go

# 假设这是从数据库查出来的结果
df = pd.DataFrame({
    "date": pd.date_range("2024-01-01", periods=5).repeat(3),
    "channel": ["A", "B", "C"] * 5,
    "pv": [100, 80, 60, 120, 90, 70, 150, 110, 80, 160, 130, 90, 180, 140, 100],
    "uv": [80, 60, 40, 90, 70, 50, 110, 85, 60, 120, 95, 65, 140, 100, 70],
    "order": [10, 8, 5, 15, 9, 6, 18, 12, 7, 20, 15, 8, 22, 16, 9],
})

# 汇总成整体趋势
daily = df.groupby("date").sum(numeric_only=True).reset_index()

fig = make_subplots(
    rows=2, cols=2,
    specs=[[{"colspan": 2}, None], [{"type": "bar"}, {"type": "domain"}]],
    subplot_titles=("整体趋势", "渠道订单数", "整体转化漏斗")
)

# 上面:整体 PV/UV 折线
fig.add_trace(
    go.Scatter(x=daily["date"], y=daily["pv"], name="PV", mode="lines+markers"),
    row=1, col=1
)
fig.add_trace(
    go.Scatter(x=daily["date"], y=daily["uv"], name="UV", mode="lines+markers"),
    row=1, col=1
)

# 左下:各渠道订单柱状图(最近一天)
latest = df[df["date"] == df["date"].max()]
fig.add_trace(
    go.Bar(x=latest["channel"], y=latest["order"], name="各渠道订单"),
    row=2, col=1
)

# 右下:转化漏斗(简单点就用三个数)
total_pv = daily["pv"].sum()
total_uv = daily["uv"].sum()
total_order = daily["order"].sum()

fig.add_trace(
    go.Funnel(
        y=["PV", "UV", "订单"],
        x=[total_pv, total_uv, total_order],
        textinfo="value+percent initial"
    ),
    row=2, col=2
)

fig.update_layout(
    title="活动数据监控总览(Demo)",
    height=600
)

fig.show()

这个东西丢给运营之后,基本能撑一阵子:他们要看趋势,可以在上面那块缩放;要看当天哪个渠道拉垮,一眼看到柱状图就行;漏斗那块至少可以大致感知有没有明显的掉血。

后来他们说想把“按渠道过滤”做成下拉框,其实用 Plotly 的 updatemenusdropdown 搞一搞就行,或者懒一点直接上 Dash 做个小页面也能完成。

动画效果:让数据“动”起来

有一次数据组的小伙伴在写一个“城市热力”演示,说想做一个“时间推移”的效果,我第一反应是:这不就是动画嘛。Plotly 其实这块写得还挺顺手。

比如做一个随时间变化的折线动画,很简单这样:

import plotly.express as px
import pandas as pd

df = pd.DataFrame({
    "day": list(range(1, 8)) * 2,
    "value": [10, 12, 15, 18, 17, 20, 23, 8, 9, 11, 14, 13, 15, 16],
    "group": ["A"] * 7 + ["B"] * 7
})

fig = px.line(
    df,
    x="day",
    y="value",
    color="group",
    animation_frame="day",  # 按天推进动画
    range_y=[0, 25],
    title="两组数据随时间变化的动画"
)

fig.show()

你会看到底下有个时间条,可以拖着看每一天的状态。虽然这个例子很简陋,但你把“day”换成“日期”,“value”换成“交易额”,动画效果一上,秒变“业务增长故事片”,在汇报时非常抓人眼球。这种动态演示也是当前 智能 & 数据 & 云 应用中的一个小亮点。

如何整合到 Web 项目与报告文档

我后来就有个非常爽的用法:每周要给领导发周报,以前是 markdown + 几张静态图。用 Plotly 之后,我直接输出一个 HTML,把几个图拼成一个页面发过去。

导出其实就一句话:

fig.write_html("weekly_report_demo.html", include_plotlyjs="cdn")

或者你多张图的时候,可以写一个很简陋的“报告生成脚本”:

import plotly.express as px
import pandas as pd

df = px.data.gapminder().query("year == 2007")

fig1 = px.scatter(df, x="gdpPercap", y="lifeExp",
                  size="pop", color="continent",
                  hover_name="country", log_x=True,
                  title="人均GDP vs 寿命")

fig2 = px.bar(df.sort_values("gdpPercap", ascending=False).head(10),
              x="country", y="gdpPercap",
              title="人均GDP前10国家")

fig1.write_html("report.html", include_plotlyjs="cdn")
# 追加第二个图到同一个 html
with open("report.html", "a", encoding="utf-8") as f:
    f.write(fig2.to_html(full_html=False, include_plotlyjs=False))

你把这个 HTML 丢到飞书、企微、邮件,领导点开就能自己玩图。而且这东西挂到任何 Web 容器里就是一个前端页面,部署上去就能用。

要是你们团队再折腾一点,上 Dash(也是 Plotly 家的)做一个简易 BI,基本就是 Python 写后端顺手把前端交互做完那种感觉,不用强行学一遍前端框架也能搞出还不错的结果页面。

实践中的一些“小坑”

这个库再“省心”,也有几个地方容易翻车,我自己踩过几次:

一个是中文字体。你用默认配置画中文,偶尔会遇到某些环境字体发虚或者直接方块字,这时候就要自己指定:

fig.update_layout(
    font=dict(family="Microsoft YaHei", size=14)
)

服务器是 Linux 的话还得保证系统真有这个字体,不然你以为“我配好了”,实际导出来还是奇奇怪怪。

还有一个是数据量的问题。Plotly 本质上是前端渲染,几万点没啥事,几十万、上百万点直接喂进去,浏览器就开始拉胯了,鼠标一挪卡半天。这种情况我一般是:

  • 要么后端先按时间或分位数抽样一遍,只给前端 1/10 的点
  • 要么就丢给专门的大数据可视化方案,不强行让 Plotly 背锅

最后一个小点是:你在 Notebook 里看图很爽,一旦放到脚本里跑,有时候会忘了写 fig.show(),然后只剩一脸懵逼的“咦怎么没有图”。还有就是远程服务器跑脚本的时候,别去期待像本地一样弹个窗口,老老实实导 HTML 或者静态图:

fig.write_image("chart.png")   # 需要先安装 kaleido
# pip install -U kaleido

反正我现在但凡是跟“图”有点关系,又不想写前端的时候,第一反应就是先拉个 Plotly 跑一跑,能解决 80% 的交互需求,还顺带把“汇报体验”升级了一档。如果你也在寻找高效制作交互式图表的方法,不妨到 云栈社区 与其他开发者交流更多实战技巧。




上一篇:使用FastAPI构建微服务实战指南:异步、安全与部署
下一篇:ToT思维树详解:Prompt工程实战与复杂问题解决指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-16 00:35 , Processed in 0.205079 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表