快轉到主要內容
  1. 教學文章/

Python Plotly 實戰:互動式資料視覺化與 Dashboard 圖表

·7 分鐘· loading · loading · ·
Python Plotly Data-Visualization Dashboard Data-Analysis
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 63: 本文

featured

一. 前言:圖表不是截圖,是探索工具
#

做資料分析時,靜態圖很夠用。 你畫一張折線圖,看趨勢。 你畫一張長條圖,看分類差異。 你畫一張散佈圖,看兩個欄位是不是有關係。

但只要資料稍微複雜一點,靜態圖就會開始卡。

  • 想知道某個點的原始數值,要回去查表。
  • 想切換地區或產品,只能重新跑一段程式。
  • 想把圖丟給同事,對方沒有 Python 環境就打不開。
  • 想做 Dashboard,又還不想直接開一個 Web app 專案。

這時候 Plotly 很好用。

Plotly 的定位不是「比較漂亮的 matplotlib」而已。 它真正香的地方是:圖表預設就能 hover、zoom、pan、選取、顯示圖例、輸出成 HTML。 也就是說,圖表本身就能承載一部分探索流程。

拍拍君之前在 Streamlit 入門篇Streamlit 進階篇 都有提到 Plotly。 不過那兩篇重點是 Streamlit App。 今天這篇反過來:先不管 Web UI,專心把 Plotly 本身練順。

我們會用一份小型銷售資料,逐步做出 Plotly Express 圖表、好讀的 hover tooltip、篩選後作圖、subplots,以及可寄給別人的 HTML 報表。 學完以後,你就可以把 Plotly 當成資料探索的日常工具。 等哪天真的需要互動 App,再接 Streamlit、Dash 或其他前端都不遲。

二. 安裝:先把環境弄乾淨
#

先建立一個小專案。 拍拍君這裡用 uv;不用 uv 的話,傳統 venv 也可以。

uv init plotly-demo
cd plotly-demo
uv add plotly pandas numpy

如果你用一般虛擬環境:

python -m venv .venv
source .venv/bin/activate
pip install plotly pandas numpy

確認版本:

python -c "import plotly; print(plotly.__version__)"

Plotly 有兩個常用入口:plotly.express 是高階 API,適合快速從 DataFrame 畫圖;plotly.graph_objects 是低階 API,適合細部控制 trace、layout、subplots。 實務上不是二選一。拍拍君通常會先用 Plotly Express 做出第一版,如果要調得更細,再把它當成 Figure 物件繼續修改。

三. 準備範例資料:不要一開始就用玩具資料
#

我們先做一份小型銷售資料。 欄位包含日期、地區、產品、營收、訂單數、廣告花費。 這種資料很適合示範 Dashboard 圖表,因為它有時間、分類和數值。

建立 demo_data.py

from __future__ import annotations

import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
dates = pd.date_range("2026-01-01", periods=120, freq="D")
regions = ["北部", "中部", "南部", "東部"]
products = ["拍拍豆", "拍拍杯", "拍拍貼紙"]

rows = []
for date in dates:
    for region in regions:
        for product in products:
            base = {"拍拍豆": 4200, "拍拍杯": 2600, "拍拍貼紙": 1200}[product]
            factor = {"北部": 1.25, "中部": 1.0, "南部": 0.9, "東部": 0.55}[region]
            revenue = max(200, base * factor * rng.normal(1.0, 0.12))
            orders = max(1, int(revenue / rng.uniform(180, 340)))
            rows.append({
                "date": date,
                "region": region,
                "product": product,
                "revenue": round(revenue, 2),
                "orders": orders,
                "ad_spend": round(revenue * rng.uniform(0.06, 0.18), 2),
            })

pd.DataFrame(rows).to_csv("sales.csv", index=False)

執行:

uv run python demo_data.py

先看一下資料:

import pandas as pd

df = pd.read_csv("sales.csv", parse_dates=["date"])
print(df.head())

這裡有個小習慣很重要: 日期欄位要在讀取時轉成 datetime。 Plotly 可以處理字串日期,但 datetime 會讓排序、時間軸和 hover 顯示更穩。

四. Plotly Express:先用最短路徑畫出來
#

先從最常用的 Plotly Express 開始。 建立 first_chart.py

import pandas as pd
import plotly.express as px

df = pd.read_csv("sales.csv", parse_dates=["date"])

daily = (
    df.groupby("date", as_index=False)
    .agg(revenue=("revenue", "sum"), orders=("orders", "sum"))
)

fig = px.line(
    daily,
    x="date",
    y="revenue",
    title="每日總營收",
    markers=True,
)

fig.show()

執行:

uv run python first_chart.py

如果你在一般桌面環境,Plotly 會用瀏覽器打開互動圖。 你可以 hover 看數值、拖曳縮放、雙擊回復範圍。

Plotly Express 的核心直覺是:

  • DataFrame 是資料來源。
  • x、y、color、size、facet 這些參數是視覺編碼。
  • 回傳值是 Figure,可以繼續修改。

所以要依產品分組,只要加上 color

product_daily = (
    df.groupby(["date", "product"], as_index=False)
    .agg(revenue=("revenue", "sum"))
)

fig = px.line(
    product_daily,
    x="date",
    y="revenue",
    color="product",
    title="各產品每日營收",
)

fig.show()

這比手動建立多條線容易很多。 而且圖例預設可以點擊開關,對探索資料很方便。

五. hover 資訊:讓圖表自己會說話
#

互動圖表最容易被低估的是 hover tooltip。 如果 hover 只顯示 x 和 y,那其實只比靜態圖多一點點互動。 真正好用的圖,hover 應該回答使用者下一秒想問的問題。

例如營收趨勢圖,使用者通常也想知道訂單數、廣告花費和平均客單價。

df["aov"] = df["revenue"] / df["orders"]

daily_region = (
    df.groupby(["date", "region"], as_index=False)
    .agg(
        revenue=("revenue", "sum"),
        orders=("orders", "sum"),
        ad_spend=("ad_spend", "sum"),
        aov=("aov", "mean"),
    )
)

fig = px.line(
    daily_region,
    x="date",
    y="revenue",
    color="region",
    title="各地區每日營收",
    labels={
        "date": "日期",
        "revenue": "營收",
        "region": "地區",
        "orders": "訂單數",
        "ad_spend": "廣告花費",
        "aov": "平均客單價",
    },
    hover_data={
        "orders": ":,",
        "ad_spend": ":,.0f",
        "aov": ":,.1f",
        "revenue": ":,.0f",
    },
)

fig.show()

labels 負責把欄位名稱翻成人類看得懂的字。 hover_data 負責決定哪些欄位出現在 tooltip 裡,以及數字格式。

拍拍君建議一開始就整理 labels。 因為圖表如果要給別人看,欄位名稱像 ad_spendaov 這種工程內部縮寫,會讓讀者慢半拍。

六. 篩選資料:互動不一定要靠 UI 框架
#

很多人想到互動,就立刻想到 Streamlit 或 Dash。 但資料探索時,最重要的互動常常只是「先篩資料,再畫圖」。

filtered = df[
    (df["region"].isin(["北部", "中部"]))
    & (df["product"] == "拍拍豆")
    & (df["date"] >= "2026-02-01")
]

trend = (
    filtered.groupby(["date", "region"], as_index=False)
    .agg(revenue=("revenue", "sum"))
)

fig = px.line(
    trend,
    x="date",
    y="revenue",
    color="region",
    title="拍拍豆:北部與中部營收趨勢",
)

fig.update_layout(template="plotly_white", hovermode="x unified")
fig.show()

篩選條件未來可以來自 Streamlit sidebar、設定檔或 CLI 參數。 但畫圖函式不要直接綁死 UI,後面才好重用。

七. Subplots:把重點放在同一張報表裡
#

Dashboard 圖表不一定要每張圖分開。 如果你想把營收趨勢和地區比較放在同一個 HTML 裡,可以用 make_subplots

from plotly.subplots import make_subplots
import plotly.graph_objects as go

daily = df.groupby("date", as_index=False).agg(revenue=("revenue", "sum"))
region = df.groupby("region", as_index=False).agg(revenue=("revenue", "sum"))

fig = make_subplots(
    rows=1,
    cols=2,
    subplot_titles=("每日營收", "各地區營收"),
)

fig.add_trace(
    go.Scatter(x=daily["date"], y=daily["revenue"], mode="lines", name="每日營收"),
    row=1,
    col=1,
)
fig.add_trace(
    go.Bar(x=region["region"], y=region["revenue"], name="地區營收"),
    row=1,
    col=2,
)

fig.update_layout(
    title="拍拍商店營運總覽",
    template="plotly_white",
    height=480,
    showlegend=False,
)
fig.show()

Subplots 的優點是集中,缺點是每張小圖空間變少。 如果使用者需要比較趨勢,放一起很好。 如果每張圖都需要仔細 hover 和縮放,分開反而比較舒服。

八. 匯出 HTML:讓圖表離開你的電腦
#

Plotly 很實用的一點是可以直接輸出 HTML。 這樣對方不用裝 Python,也能打開互動圖。

fig.write_html(
    "sales-dashboard.html",
    include_plotlyjs="cdn",
    full_html=True,
)

“cdn” 會讓 HTML 比較小,但打開時需要網路。 如果要寄給不能確定網路環境的人,可以把 include_plotlyjs 改成 True,讓檔案離線可開。

也可以輸出靜態圖片。 這需要 kaleido:

uv add kaleido
fig.write_image("sales-dashboard.png", scale=2)

靜態圖適合簡報、文件、社群貼文。 互動 HTML 適合探索、內部報表和可點擊的分析附件。

九. 常見坑:Plotly 很強,但不要亂用
#

Plotly 最常見的坑不是語法,而是資料量和圖表設計。

第一,點太多會卡;幾十萬個點應該先聚合、抽樣或篩選,不要全部丟進瀏覽器。 第二,類別太多會亂;如果 color 有 80 種分類,圖例會變成災難,先保留前幾名,把其他合併成「其他」。 第三,雙 y 軸要節制。 雙 y 軸很方便,但也很容易誤導。 只有在兩個指標真的需要同時看趨勢,且單位差異明確時才用。

第四,hover 不要塞成履歷表。 tooltip 應該補足圖表,不是把整列資料全倒出來。 一個好 hover 通常 3 到 6 個欄位就夠。

第五,圖表標題要寫結論,不只寫欄位。 「每日營收」可以用。 但「北部週末營收明顯高於平日」更有分析價值。

結語
#

Plotly 很適合放在 Python 資料工具箱裡。 它不像完整 BI 工具那樣需要一堆設定,也不像靜態圖那樣只能看不能摸。 它剛好站在中間:用 Python 寫、用瀏覽器互動、用 HTML 分享。

今天的重點可以濃縮成幾句:

  • 快速探索先用 Plotly Express。
  • 需要細部控制再用 Graph Objects。
  • hover、labels、template 會大幅影響圖表可讀性。
  • 資料篩選和畫圖邏輯要分開,之後才好接 App。
  • 匯出 HTML 是 Plotly 的大招,內部報表超好用。

下次你做完一份資料分析,先不要急著截圖。 試著把圖表做成可以 hover、zoom、分享的互動 HTML。 讀者能自己探索,圖表就不只是結果,而是工具。

延伸閱讀
#

Python 學習 - 本文屬於一個選集。
§ 63: 本文

相關文章

Streamlit:用 Python 快速打造互動式資料應用
·8 分鐘· loading · loading
Python Streamlit Data-Visualization Web-App Dashboard
Streamlit 進階:session_state、cache 與多頁 Dashboard 完全攻略
·11 分鐘· loading · loading
Python Streamlit Dashboard Data-App Cache Session-State
Python DuckDB 實戰:用 SQL 快速分析 CSV 與 Parquet
·6 分鐘· loading · loading
Python Duckdb SQL Parquet Data-Analysis Developer-Tools
Python marimo 實戰:可重現的 Reactive Notebook 與資料小工具
·7 分鐘· loading · loading
Python Marimo Notebook Data-App Developer-Tools Reactive
Rich + Typer:打造漂亮又好用的 Python CLI 體驗
·10 分鐘· loading · loading
Python Rich Typer Cli Command-Line Developer-Tools
Python argparse 實戰:CLI 參數解析、旗標設計與 subcommands 完全攻略
·9 分鐘· loading · loading
Python Argparse Cli Command-Line Automation Developer-Tools