Plotly 是一个基于 JavaScript 的 Python 交互式可视化库,核心渲染引擎为 plotly.js,支持在 Jupyter Notebook、JupyterLab、HTML 文件以及 Dash Web 应用等多种环境中展示交互式图表。与 Matplotlib、Seaborn 等静态绘图库不同,Plotly 生成的图表天然具备交互能力——鼠标悬停显示数据细节、框选缩放、平移拖拽、点击触发回调等,极大提升了数据探索的效率和直观性。
Plotly 提供了两套主要 API:plotly.express(简称 px)是高层接口,语法简洁,一行代码即可生成美观的统计图表;plotly.graph_objects(简称 go)是底层接口,允许对图表的每个元素进行精细控制。两套 API 完全互通——px 返回的 Figure 对象可以继续用 go 的方法进行修改和定制。
安装方式:
# 核心库
pip install plotly
# Jupyter 渲染支持
pip install ipywidgets nbformat
# Dash Web 应用
pip install dash
Plotly 的核心数据结构是 Figure 对象,它包含三个组成部分:data(一个或多个 Trace 的列表)、layout(图表布局属性)和 frames(动画帧)。无论是 px 还是 go,最终都生成 Figure 对象,通过 fig.show() 渲染输出,通过 fig.write_html() 保存为独立的 HTML 文件。
plotly.express 是 Plotly 的高层封装,设计理念效仿 Seaborn——用最少的代码完成最常见的可视化任务。px 内部自动处理数据聚合、颜色映射、图例生成等细节,非常适合快速探索性数据分析(EDA)。px 的所有函数都接受 Pandas DataFrame 作为数据源,与数据分析工作流无缝衔接。
散点图用于展示两个连续变量之间的关系,是数据分析中最基础的图表类型。通过 px.scatter 可以轻松添加颜色、大小、分类等额外维度。
import plotly.express as px
import pandas as pd
# 加载内置数据集
df = px.data.iris()
# 散点图:花瓣长度 vs 花瓣宽度,按物种着色
fig = px.scatter(
df,
x="petal_length",
y="petal_width",
color="species",
size="sepal_length",
hover_data=["sepal_width"],
title="Iris 数据集:花瓣尺寸分布"
)
fig.show()
折线图适合展示时间序列或连续变量的变化趋势。px.line 支持多组线条绘制,自动按分类变量分组着色。
import plotly.express as px
df = px.data.gapminder()
df_2007 = df.query("year == 2007")
# 折线图:展示各国人均GDP随时间变化
fig = px.line(
df.query("country == 'China' or country == 'United States' or country == 'Japan'"),
x="year",
y="gdpPercap",
color="country",
markers=True,
title="中美日三国人均GDP变化趋势"
)
fig.show()
柱状图用于比较分类变量的数值大小。px.bar 支持分组柱状图和堆叠柱状图,通过 barmode 参数控制。
import plotly.express as px
df = px.data.tips()
# 分组柱状图:不同性别在不同时间段的消费情况
fig = px.bar(
df,
x="day",
y="total_bill",
color="sex",
barmode="group",
title="一周各天消费金额(按性别分组)",
text_auto=".2f"
)
fig.show()
直方图展示数值变量的分布情况。px.histogram 自动进行分箱统计,支持按类别分组叠加。
import plotly.express as px
df = px.data.tips()
# 直方图:消费金额分布
fig = px.histogram(
df,
x="total_bill",
color="sex",
nbins=30,
marginal="rug",
title="消费金额分布直方图"
)
fig.show()
箱线图和小提琴图用于展示数值变量在不同类别下的分布特征,包括中位数、四分位数、离群点等信息。小提琴图还显示了核密度估计,提供更丰富的分布形状信息。
import plotly.express as px
df = px.data.tips()
# 箱线图
fig_box = px.box(
df,
x="day",
y="total_bill",
color="sex",
title="各天消费金额箱线图"
)
fig_box.show()
# 小提琴图
fig_violin = px.violin(
df,
x="day",
y="total_bill",
color="sex",
box=True,
points="all",
title="各天消费金额小提琴图"
)
fig_violin.show()
这两种图表适合展示两个连续变量的密度分布,尤其适用于数据点密集重叠的场景。密度热力图用颜色矩形网格表示密度,密度等高线则用等高线表示。
import plotly.express as px
df = px.data.iris()
# 密度热力图
fig_heat = px.density_heatmap(
df,
x="sepal_width",
y="sepal_length",
color_continuous_scale="Viridis",
title="花萼宽度 vs 花萼长度 密度热力图"
)
fig_heat.show()
# 密度等高线
fig_contour = px.density_contour(
df,
x="sepal_width",
y="sepal_length",
color="species",
title="花萼宽度 vs 花萼长度 密度等高线"
)
fig_contour.show()
px 的核心理念是"智能默认值"(Smart Defaults)。每一类图表都有经过精心调整的默认参数——颜色方案使用 Plotly 专属色板,坐标轴自动适配数据类型,图例自动生成并定位,悬停信息包含所有数据维度的取值。这使得 px 特别适合快速原型开发和探索性分析,用户可以用极少的代码获得高质量的交互式图表。
当 px 的默认行为无法满足精细定制需求时,就需要使用 plotly.graph_objects 模块。go 提供了完全面向对象的 API,允许直接操作 Figure 的每个组成部分:Trace、Layout、Axis、Annotation 等。所有 px 生成的 Figure 对象底层仍然是 go 对象,因此两者可以无缝混合使用。
import plotly.graph_objects as go
# 创建一个 Figure 并添加 Scatter trace
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[1, 2, 3, 4, 5],
y=[2, 5, 3, 7, 4],
mode="lines+markers+text",
name="数据系列A",
text=["点1", "点2", "点3", "点4", "点5"],
textposition="top center",
line=dict(color="royalblue", width=3, dash="solid"),
marker=dict(size=10, symbol="circle", line=dict(width=2, color="DarkSlateGrey"))
))
fig.show()
import plotly.graph_objects as go
# 添加 Bar trace
fig = go.Figure()
fig.add_trace(go.Bar(
x=["产品A", "产品B", "产品C", "产品D"],
y=[320, 480, 250, 610],
marker_color=["#3498db", "#e74c3c", "#2ecc71", "#f39c12"],
text=["320万", "480万", "250万", "610万"],
textposition="auto"
))
# 使用 update_layout 精细控制布局
fig.update_layout(
title="2024年各产品销售额(万元)\n各产品销量对比",
title_font=dict(size=22, family="Microsoft YaHei"),
xaxis=dict(title="产品名称", title_font=dict(size=14)),
yaxis=dict(title="销售额(万元)", title_font=dict(size=14),
gridcolor="lightgray"),
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
hovermode="x unified",
showlegend=False
)
fig.show()
import plotly.graph_objects as go
import numpy as np
# 创建多个 trace
fig = go.Figure()
x = np.linspace(0, 2 * np.pi, 100)
fig.add_trace(go.Scatter(x=x, y=np.sin(x), name="sin(x)"))
fig.add_trace(go.Scatter(x=x, y=np.cos(x), name="cos(x)"))
fig.add_trace(go.Scatter(x=x, y=np.sin(x) * np.cos(x), name="sin(x)*cos(x)"))
# 批量修改所有 trace 的属性
fig.update_traces(
mode="lines",
line=dict(width=2.5),
hovertemplate="x=%{x:.2f}
y=%{y:.2f} "
)
fig.update_layout(
title="三角函数曲线",
xaxis=dict(title="x", dtick=np.pi/2,
ticktext=["0", "π/2", "π", "3π/2", "2π"]),
legend=dict(orientation="h", y=1.02)
)
fig.show()
go 与 px 的协作模式:先用 px 快速生成初始图表,再通过 fig.update_layout()、fig.update_traces()、fig.update_xaxes() 等方法进行精细化调整。这种"先快后精"的工作流程兼顾了开发效率和定制灵活性,是实际项目中最常用的模式。
交互性是 Plotly 区别于静态绘图库的核心优势。这些交互功能无需额外编码即可默认启用,但也可以通过参数进行精细配置,以满足特定场景的需求。
鼠标悬停在数据点上时显示详细信息,支持自定义模板(hovertemplate)和统一悬停模式(hovermode)。
框选缩放(dragmode="zoom")和滚轮缩放,支持指定坐标轴缩放(xzoom/yzoom)和自动缩放重置(双击)。
通过 dragmode="pan" 切换为平移模式,在图表区域内拖拽移动视图,适合大范围数据浏览。
在 Dash 应用中通过 clickData 捕获点击事件,实现图表间的联动交互和数据下钻。
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[1, 2, 3, 4, 5],
y=[10, 20, 15, 25, 30],
# 自定义悬停模板:使用 %{variable} 占位符
hovertemplate="<b>日期:</b>%{x}<br>"
+ "<b>数值:</b>%{y} 万元<br>"
+ "<extra>点击查看详情</extra>"
))
fig.update_layout(
hovermode="x unified", # 统一悬停:所有 series 在同一 x 值显示
title="自定义悬停信息示例"
)
fig.show()
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# 生成时间序列数据
dates = pd.date_range("2024-01-01", periods=365, freq="D")
values = np.random.randn(365).cumsum() + 100
fig = go.Figure()
fig.add_trace(go.Scatter(x=dates, y=values, mode="lines"))
# 添加 Range Slider
fig.update_layout(
title="时间序列数据(带范围选择器)",
xaxis=dict(
rangeslider=dict(visible=True),
type="date"
),
# 添加快捷时间范围按钮
updatemenus=[dict(
type="buttons",
direction="right",
x=0, y=1.15,
buttons=[
dict(label="全部", method="relayout", args=[{"xaxis.range": None}]),
dict(label="1个月", method="relayout", args=[{"xaxis.range": [dates[0], dates[30]]}]),
dict(label="3个月", method="relayout", args=[{"xaxis.range": [dates[0], dates[90]]}]),
dict(label="6个月", method="relayout", args=[{"xaxis.range": [dates[0], dates[180]]}),
]
)]
)
fig.show()
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(
x=[1, 2, 3, 4, 5],
y=[1, 4, 9, 16, 25],
mode="lines+markers"
))
# config 参数控制图表交互行为
fig.show(config=dict(
scrollZoom=True, # 启用滚轮缩放
displayModeBar=True, # 显示模式栏
displaylogo=False, # 隐藏 Plotly 图标
modeBarButtonsToRemove=[ # 移除不需要的按钮
"sendDataToCloud",
"lasso2d",
"select2d"
],
toImageButtonOptions=dict( # 导出图片配置
format="png",
width=1200,
height=800
)
))
在数据分析中,经常需要将多个图表组合在一个视图中进行比较。Plotly 的 make_subplots 函数支持创建网格布局的子图,每个子图可以包含不同类型的 trace,并且可以跨行跨列合并(specs 参数)。
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import numpy as np
# 创建 2 行 2 列的子图,指定子图类型
fig = make_subplots(
rows=2, cols=2,
specs=[
[{"type": "scatter"}, {"type": "bar"}],
[{"type": "pie"}, {"type": "histogram"}]
],
subplot_titles=(
"散点图", "柱状图",
"饼图", "直方图"
),
vertical_spacing=0.12,
horizontal_spacing=0.1
)
# 向指定位置添加 trace
x = np.linspace(0, 2*np.pi, 50)
fig.add_trace(go.Scatter(x=x, y=np.sin(x)), row=1, col=1)
fig.add_trace(go.Bar(x=["A","B","C"], y=[10,20,15]), row=1, col=2)
fig.add_trace(go.Pie(labels=["分类1","分类2","分类3"], values=[30,40,30]), row=2, col=1)
fig.add_trace(go.Histogram(x=np.random.randn(200)), row=2, col=2)
# 统一更新布局
fig.update_layout(
title_text="多种图表类型组合子图",
height=700,
showlegend=False
)
fig.show()
子图高级用法:
Plotly 的动画功能允许展示数据随时间或其他维度的变化过程。核心参数 animation_frame 指定驱动动画的变量,Plotly 会自动生成每一帧的数据切片,并添加 slider 滑块和 play 播放按钮。
import plotly.express as px
df = px.data.gapminder()
# 气泡图动画:展示全球各国 1952-2007 年发展变化
fig = px.scatter(
df,
x="gdpPercap",
y="lifeExp",
size="pop",
color="continent",
hover_name="country",
log_x=True,
size_max=60,
animation_frame="year", # 以年份驱动动画
animation_group="country",
range_x=[100, 100000],
range_y=[25, 90],
title="全球各国发展变化(1952-2007)"
)
# 调整动画播放速度
fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 500
fig.show()
import plotly.graph_objects as go
import numpy as np
# 使用 frames 属性手动构建动画
x = np.linspace(0, 2*np.pi, 50)
fig = go.Figure(
data=[go.Scatter(x=x, y=np.sin(x), mode="lines", line=dict(color="blue"))],
layout=go.Layout(
title="正弦波相位移动动画",
xaxis=dict(range=[0, 2*np.pi]),
yaxis=dict(range=[-1.5, 1.5]),
updatemenus=[dict(
type="buttons",
showactive=False,
buttons=[dict(
label="播放",
method="animate",
args=[None, dict(frame=dict(duration=50, redraw=True), fromcurrent=True)]
)]
)]
),
frames=[go.Frame(
data=[go.Scatter(x=x, y=np.sin(x + phase), line=dict(color="blue"))],
name=f"frame_{i}"
) for i, phase in enumerate(np.linspace(0, 2*np.pi, 50))]
)
fig.show()
Dash 是 Plotly 公司开发的 Python Web 应用框架,基于 Flask + React + plotly.js 构建。使用 Dash,数据分析师可以仅用 Python 代码创建包含交互式图表的 Web 仪表盘,无需编写任何 JavaScript 或 HTML。
from dash import Dash, dcc, html
import plotly.express as px
# 创建 Dash 应用实例
app = Dash(__name__)
# 加载数据
df = px.data.iris()
# 创建图表
fig = px.scatter(
df, x="sepal_width", y="sepal_length",
color="species", size="petal_length"
)
# 布局:使用 html 和 dcc 组件构建页面
app.layout = html.Div([
html.H1("Iris 数据集可视化仪表盘",
style={"textAlign": "center", "color": "#2c3e50"}),
html.P("使用 Dash 和 Plotly 构建的交互式数据可视化应用",
style={"textAlign": "center"}),
dcc.Graph(
id="iris-scatter",
figure=fig,
style={"height": "600px"}
)
])
if __name__ == "__main__":
app.run(debug=True)
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import pandas as pd
app = Dash(__name__)
df = px.data.gapminder()
app.layout = html.Div([
html.H1("全球发展数据交互仪表盘"),
html.Label("选择年份:"),
dcc.Slider(
id="year-slider",
min=df["year"].min(),
max=df["year"].max(),
value=df["year"].max(),
marks={str(y): str(y) for y in df["year"].unique()},
step=None
),
dcc.Graph(id="gapminder-graph")
])
# 回调:根据 slider 值更新图表
@app.callback(
Output("gapminder-graph", "figure"),
[Input("year-slider", "value")]
)
def update_figure(selected_year):
filtered_df = df[df.year == selected_year]
fig = px.scatter(
filtered_df,
x="gdpPercap", y="lifeExp",
size="pop", color="continent",
hover_name="country", log_x=True,
size_max=55,
title=f"{selected_year} 年各国数据"
)
return fig
if __name__ == "__main__":
app.run(debug=True)
| 模块 | 常用组件 | 说明 |
|---|---|---|
| dash_core_components | dcc.Graph, dcc.Slider, dcc.Dropdown, dcc.Input, dcc.RangeSlider, dcc.Store | 核心交互组件,包括图表、滑块、下拉框、输入框等 |
| dash_html_components | html.Div, html.H1, html.P, html.Button, html.Table | HTML 标签的 Python 封装,用于页面布局 |
| dash.dependencies | Input, Output, State | 定义回调函数的输入输出依赖 |
"Dash 让 Python 数据分析师无需学习前端技术栈即可构建完整的数据应用。其核心理念是"声明式布局 + 响应式回调"——用 Python 字典/对象描述界面,用装饰器函数定义交互逻辑。"
Plotly 支持多种导出方式,方便将交互式图表嵌入到不同平台中。
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
# 1. 导出为独立 HTML 文件(完整交互功能)
fig.write_html("iris_plot.html")
# 2. 导出为静态图片(需要 kaleido 或 orca)
# pip install -U kaleido
fig.write_image("iris_plot.png", width=1200, height=800, scale=2)
fig.write_image("iris_plot.pdf")
# 3. 导出 JSON 格式(用于再次加载或传输)
fig.write_json("iris_plot.json")
# 4. 从 JSON 重新加载
import plotly.io as pio
fig_loaded = pio.read_json("iris_plot.json")
fig_loaded.show()
当数据量较大时,Plotly 图表的渲染性能可能成为瓶颈。以下是一些经过实践验证的优化策略:
| 优化方法 | 实现方式 | 适用场景 |
|---|---|---|
| 数据降采样 | 对时间序列使用 resample 或 sample 减少数据点 | 百万级时间序列 |
| Scattergl 渲染 | 使用 go.Scattergl 替代 go.Scatter,利用 WebGL 加速 | 超过 10 万个数据点的散点图 |
| shape 简化 | 减少复杂形状和标注的数量 | 地图叠加、复杂标注 |
| 分页加载 | Dash 中结合回调按需加载数据子集 | Web 应用仪表盘 |
import plotly.graph_objects as go
import numpy as np
# 使用 Scattergl 加速大数据集渲染
n = 100000
x = np.random.randn(n)
y = np.random.randn(n)
fig = go.Figure(data=go.Scattergl(
x=x, y=y,
mode="markers",
marker=dict(
size=3,
color=np.random.randn(n),
colorscale="Viridis",
showscale=True,
opacity=0.6
)
))
fig.update_layout(
title="10 万数据点散点图(WebGL 加速渲染)"
)
fig.show()
掌握 Plotly 的最佳途径是动手实践。建议从以下几个方向逐步深入:
"数据可视化不是图表的终点,而是探索的起点。Plotly 的交互特性让用户可以在数据中自由导航,发现隐藏的模式和洞见。用好交互可视化,数据分析的效率将提升一个数量级。"