Python利用Plotly打造可缩放的数据报告
本节学习目标
完成本节学习后,你将能够:
- 理解 Plotly 的核心概念与 Plotly Express 的便捷用法
- 创建交互式散点图、柱状图、折线图
- 自定义图表样式、颜色、布局和动画
- 使用子图(subplots)布局多图表
- 为图表添加悬停信息、按钮和交互控件
- 将交互式图表导出为 HTML 文件进行分享
- 在 Jupyter Notebook 和 Web 环境中嵌入 Plotly 图表
为什么学这个
想象两份销售报告:
- 报告A:包含 20 张静态 PNG 图片,每张图展示一个维度的数据。
- 报告B:包含 3 张交互式图表,可以鼠标悬停查看具体数值、拖拽缩放某个时间段、点击图例筛选特定产品。
你会选择哪份?毫无疑问,报告B的信息密度更高,用户体验更好,决策效率更高。
这就是 Plotly 的价值所在。如果说 Matplotlib 和 Seaborn 是"拍照"——拍完就不能改了;那么 Plotly 就是"拍视频"——用户可以暂停、回放、放大看细节。
Plotly 的核心优势:
- 零门槛交互:生成的图表自带缩放、平移、悬停提示、截图等功能
- Web 原生:导出为 HTML 即可在浏览器中运行,无需额外软件
- Express 模式:一行代码创建复杂图表
- 与 Pandas 完美集成:直接传入 DataFrame
核心知识点讲解
一、Plotly Express——一行代码的魔法
Plotly Express(简称 px)是 Plotly 的高级接口,设计理念是"用最少的代码做最多的事"。它的用法与 Seaborn 非常相似——传入 DataFrame 和列名即可。
1.1 安装与导入
# 安装 Plotly(如果尚未安装) # pip install plotly pandas import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import pandas as pd import numpy as np
1.2 你的第一个交互式散点图
import plotly.express as px
import numpy as np
import pandas as pd
# 生成模拟数据
np.random.seed(42)
n = 150
df = pd.DataFrame({
'广告投入': np.random.uniform(10, 100, n),
'销售额': 0,
'产品类别': np.random.choice(['电子产品', '服装', '食品', '家居'], n),
'区域': np.random.choice(['华东', '华南', '华北', '西部'], n)
})
df['销售额'] = (2.5 * df['广告投入'] +
np.where(df['产品类别'] == '电子产品', 50, 0) +
np.where(df['区域'] == '华东', 30, 0) +
np.random.normal(0, 15, n))
# 一行代码创建交互式散点图
fig = px.scatter(df, x='广告投入', y='销售额',
color='产品类别', # 颜色按类别区分
size='销售额', # 气泡大小按销售额
hover_data=['区域'], # 悬停时显示区域信息
title='广告投入与销售额关系(交互式)',
labels={'广告投入': '广告投入(万元)', '销售额': '销售额(万元)'},
color_discrete_map={'电子产品': '#1E88E5', '服装': '#E53935',
'食品': '#43A047', '家居': '#FB8C00'})
# 设置图表布局
fig.update_layout(
font=dict(size=13),
title=dict(font=dict(size=18), x=0.5),
hovermode='closest' # 悬停时最近的数据点都高亮
)
# 显示图表(在 Jupyter 中直接显示,脚本中会打开浏览器)
fig.show()
交互式操作说明:
- 鼠标悬停:显示该数据点的详细信息
- 拖拽:平移图表
- 滚轮/框选:缩放某个区域
- 双击:重置缩放
- 点击图例:显示/隐藏对应类别
- 右上角工具栏:截图、缩放、平移、重置等功能
二、交互式柱状图
2.1 基础柱状图
import plotly.express as px
import pandas as pd
import numpy as np
# 模拟数据:各部门季度营收
df = pd.DataFrame({
'部门': ['销售部', '市场部', '技术部', '运营部', '人事部', '财务部'],
'Q1': [450, 320, 580, 290, 150, 200],
'Q2': [480, 350, 620, 310, 160, 210],
'Q3': [520, 380, 650, 330, 155, 220],
'Q4': [550, 400, 700, 350, 165, 230]
})
# 需要将宽格式转为长格式
df_melted = df.melt(id_vars='部门', var_name='季度', value_name='营收')
# 创建分组柱状图
fig = px.bar(df_melted, x='部门', y='营收', color='季度',
barmode='group', # 分组模式(还有 'stack' 堆叠模式)
title='各部门季度营收对比',
labels={'部门': '部门', '营收': '营收(万元)', '季度': '季度'},
color_discrete_sequence=['#4CAF50', '#FF9800', '#2196F3', '#9C27B0'])
fig.update_layout(
title=dict(font=dict(size=18), x=0.5),
font=dict(size=13),
xaxis_tickangle=-30, # X轴标签旋转
hovermode='x unified' # 悬停时同一X轴的所有值一起显示
)
fig.show()
2.2 水平柱状图(适合长标签)
import plotly.express as px
import pandas as pd
# 模拟数据:各产品线年度销售排名
df = pd.DataFrame({
'产品线': ['智能手机Pro Max', '笔记本电脑Ultra', '智能手表S9',
'无线耳机AirPods', '平板电脑Mini', '智能音箱Home'],
'销售额': [2850, 2100, 1650, 1420, 980, 750]
})
# 按销售额排序
df = df.sort_values('销售额', ascending=True)
fig = px.bar(df, x='销售额', y='产品线', orientation='h',
title='各产品线年度销售额排行(万元)',
color='销售额',
color_continuous_scale='Blues',
text='销售额') # 在柱子上显示数值
fig.update_layout(
title=dict(font=dict(size=16), x=0.5),
font=dict(size=13),
yaxis={'categoryorder': 'total ascending'}
)
# 添加数据标签
fig.update_traces(texttemplate='%{text:,}', textposition='outside')
fig.show()
三、交互式折线图
3.1 时间序列折线图
import plotly.express as px
import pandas as pd
import numpy as np
# 模拟数据:两年的日销售数据
np.random.seed(42)
dates = pd.date_range('2023-01-01', '2024-12-31', freq='D')
base_sales = 1000
df = pd.DataFrame({
'日期': dates,
'基础销量': base_sales + 200 * np.sin(np.arange(len(dates)) * 2 * np.pi / 365), # 季节性
'随机波动': np.random.normal(0, 100, len(dates))
})
df['销售额'] = df['基础销量'] + df['随机波动']
df['年份'] = df['日期'].dt.year.astype(str)
df['月份'] = df['日期'].dt.month
# 按月聚合
monthly = df.groupby(['年份', '月份'])['销售额'].mean().reset_index()
monthly.columns = ['年份', '月份', '平均日销售额']
fig = px.line(monthly, x='月份', y='平均日销售额', color='年份',
title='月度平均日销售额趋势对比',
labels={'月份': '月份', '平均日销售额': '平均日销售额(元)', '年份': '年份'},
markers=True,
line_shape='linear',
color_discrete_map={'2023': '#1E88E5', '2024': '#E53935'})
fig.update_layout(
title=dict(font=dict(size=16), x=0.5),
font=dict(size=13),
hovermode='x unified',
xaxis=dict(tickmode='linear', tick0=1, dtick=1, title='月份')
)
# 添加标记和填充
fig.update_traces(mode='lines+markers', marker=dict(size=8))
fig.show()
3.2 带误差线的折线图
import plotly.graph_objects as go
import numpy as np
import pandas as pd
# 模拟A/B测试数据
df = pd.DataFrame({
'周次': range(1, 9),
'A组转化率': [3.2, 3.5, 3.8, 4.0, 4.2, 4.1, 4.3, 4.5],
'A组误差': [0.3, 0.3, 0.4, 0.3, 0.3, 0.4, 0.3, 0.3],
'B组转化率': [3.1, 3.3, 3.6, 3.8, 4.0, 4.3, 4.6, 4.9],
'B组误差': [0.3, 0.3, 0.3, 0.4, 0.3, 0.3, 0.4, 0.4]
})
fig = go.Figure()
# A组
fig.add_trace(go.Scatter(
x=df['周次'], y=df['A组转化率'],
name='A组(对照组)',
mode='lines+markers',
line=dict(color='#1E88E5', width=3),
marker=dict(size=10),
error_y=dict(type='data', array=df['A组误差'], visible=True, thickness=2, width=5)
))
# B组
fig.add_trace(go.Scatter(
x=df['周次'], y=df['B组转化率'],
name='B组(实验组)',
mode='lines+markers',
line=dict(color='#E53935', width=3, dash='dash'),
marker=dict(size=10, symbol='square'),
error_y=dict(type='data', array=df['B组误差'], visible=True, thickness=2, width=5)
))
fig.update_layout(
title='A/B测试:转化率对比(含置信区间)',
title=dict(font=dict(size=16), x=0.5),
xaxis=dict(title='周次', tickmode='linear', dtick=1),
yaxis=dict(title='转化率(%)'),
font=dict(size=13),
hovermode='x unified',
legend=dict(yanchor='top', y=0.99, xanchor='left', x=0.01)
)
fig.show()
四、子图布局(Subplots)
Plotly 使用 make_subplots 创建多图表布局,用法灵活。
4.1 基础子图布局
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
# 创建 2x2 子图
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('销售额趋势', '产品占比', '区域分布', '客户评分'),
specs=[[{'type': 'xy'}, {'type': 'domain'}], # domain 用于饼图
[{'type': 'xy'}, {'type': 'xy'}]]
)
# 子图(1,1):折线图
months = ['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月']
fig.add_trace(go.Scatter(
x=months, y=[120, 135, 148, 162, 175, 190, 210, 225, 240, 230, 215, 250],
mode='lines+markers', name='销售额', line=dict(color='#1E88E5', width=2),
marker=dict(size=8)
), row=1, col=1)
# 子图(1,2):饼图
fig.add_trace(go.Pie(
labels=['电子产品', '服装', '食品', '家居'],
values=[450, 320, 280, 180],
marker=dict(colors=['#1E88E5', '#E53935', '#43A047', '#FB8C00']),
textinfo='label+percent',
hole=0.4 # 空心圆环图
), row=1, col=2)
# 子图(2,1):柱状图
fig.add_trace(go.Bar(
x=['华东', '华南', '华北', '西南', '华中', '东北'],
y=[580, 420, 380, 250, 310, 190],
name='区域',
marker=dict(color=['#1E88E5', '#E53935', '#43A047', '#FB8C00', '#9C27B0', '#00BCD4'])
), row=2, col=1)
# 子图(2,2):面积图
fig.add_trace(go.Scatter(
x=['Q1', 'Q2', 'Q3', 'Q4'], y=[60, 79, 101, 104],
fill='tozeroy', name='利润',
line=dict(color='#E53935', width=2),
fillcolor='rgba(229, 57, 53, 0.3)'
), row=2, col=2)
fig.update_layout(
title=dict(text='年度经营数据看板', x=0.5, font=dict(size=20)),
showlegend=False,
height=700,
font=dict(size=12)
)
fig.show()
4.2 复杂布局(不规则网格)
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
# 创建不规则布局:上方大图 + 下方两个小图
fig = make_subplots(
rows=3, cols=2,
row_heights=[0.5, 0.25, 0.25],
specs=[[{'colspan': 2}, None], # 第一行横跨两列
[{'type': 'xy'}, {'type': 'xy'}], # 第二行两个子图
[{'colspan': 2}, None]] # 第三行横跨两列
)
x = np.linspace(0, 10, 100)
# 上方大图:多条曲线
fig.add_trace(go.Scatter(x=x, y=np.sin(x), name='sin(x)', line=dict(color='#1E88E5')), row=1, col=1)
fig.add_trace(go.Scatter(x=x, y=np.cos(x), name='cos(x)', line=dict(color='#E53935')), row=1, col=1)
# 左下:散点
np.random.seed(42)
fig.add_trace(go.Scatter(
x=np.random.randn(100), y=np.random.randn(100),
mode='markers', name='随机数据',
marker=dict(color=np.random.randn(100), colorscale='Viridis',
size=8, showscale=True)
), row=2, col=1)
# 右下:柱状图
fig.add_trace(go.Bar(
x=['A', 'B', 'C', 'D'], y=[45, 32, 58, 29],
marker_color=['#4CAF50', '#FF9800', '#2196F3', '#9C27B0']
), row=2, col=2)
# 底部:面积图
fig.add_trace(go.Scatter(
x=list(range(30)), y=np.cumsum(np.random.randn(30)),
fill='tozeroy', fillcolor='rgba(229, 57, 53, 0.3)',
line=dict(color='#E53935')
), row=3, col=1)
fig.update_layout(
title=dict(text='综合数据看板', x=0.5, font=dict(size=18)),
height=900,
font=dict(size=12)
)
fig.show()
五、高级交互功能
5.1 自定义悬停信息
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# 模拟产品数据
np.random.seed(42)
df = pd.DataFrame({
'产品': [f'产品{i}' for i in range(1, 16)],
'销售额': np.random.randint(100, 500, 15),
'利润率': np.random.uniform(10, 40, 15),
'增长率': np.random.uniform(-10, 30, 15),
'库存天数': np.random.randint(5, 60, 15)
})
fig = go.Figure()
fig.add_trace(go.Scatter(
x=df['销售额'], y=df['利润率'],
mode='markers',
marker=dict(
size=df['库存天数'] / 2, # 气泡大小
color=df['增长率'], # 颜色
colorscale='RdYlGn', # 红黄绿渐变
showscale=True,
colorbar=dict(title='增长率(%)')
),
# 自定义悬停模板
hovertemplate=
'<b>%{text}</b><br><br>' +
'销售额: %{x:,.0f} 万元<br>' +
'利润率: %{y:.1f}%<br>' +
'库存天数: %{marker.size:.0f} 天<br>' +
'<extra></extra>', # 隐藏 trace name
text=df['产品']
))
fig.update_layout(
title='产品矩阵分析(气泡=库存,颜色=增长率)',
title=dict(x=0.5, font=dict(size=16)),
xaxis=dict(title='销售额(万元)', gridcolor='lightgray'),
yaxis=dict(title='利润率(%)', gridcolor='lightgray'),
font=dict(size=13)
)
fig.show()
5.2 添加按钮和下拉选择器
import plotly.graph_objects as go
import numpy as np
# 生成数据:不同产品的销售曲线
np.random.seed(42)
months = [f'{i}月' for i in range(1, 13)]
fig = go.Figure()
products = {
'产品A': np.cumsum(np.random.normal(20, 10, 12)) + 100,
'产品B': np.cumsum(np.random.normal(15, 8, 12)) + 80,
'产品C': np.cumsum(np.random.normal(25, 12, 12)) + 120
}
for name, data in products.items():
fig.add_trace(go.Scatter(
x=months, y=data, name=name,
mode='lines+markers', visible=True
))
# 创建按钮
buttons = []
for i, name in enumerate(products.keys()):
visibility = [False] * len(products)
visibility[i] = True
buttons.append(dict(
label=name,
method='update',
args=[{'visible': visibility},
{'title': f'{name} 月度销售趋势'}]
))
# 添加"全部显示"按钮
buttons.append(dict(
label='全部显示',
method='update',
args=[{'visible': [True] * len(products)},
{'title': '所有产品月度销售趋势'}]
))
fig.update_layout(
updatemenus=[dict(
buttons=buttons,
direction='down',
showactive=True,
x=0.1, y=1.15,
xanchor='left', yanchor='top',
bgcolor='white',
bordercolor='gray'
)],
title=dict(text='产品趋势查看器', x=0.5, font=dict(size=16)),
font=dict(size=13)
)
fig.show()
六、动画效果
Plotly 可以为图表添加动画,让数据"动"起来。
import plotly.express as px
import pandas as pd
import numpy as np
# 模拟数据:各区域月度销售额变化(带动画)
np.random.seed(42)
records = []
for month in range(1, 13):
for region in ['华东', '华南', '华北', '西南', '华中']:
base = {'华东': 500, '华南': 350, '华北': 300, '西南': 200, '华中': 250}[region]
growth = month * 20
records.append({
'区域': region,
'月份': f'{month}月',
'月序号': month,
'销售额': base + growth + np.random.normal(0, 30),
'利润率': np.random.uniform(15, 35)
})
df = pd.DataFrame(records)
# 创建带动画的散点图
fig = px.scatter(df, x='销售额', y='利润率',
animation_frame='月序号', # 动画按月份推进
animation_group='区域',
color='区域',
size='销售额',
range_x=[100, 800],
range_y=[10, 40],
title='各区域销售额与利润率月度演变',
labels={'销售额': '销售额(万元)', '利润率': '利润率(%)'},
color_discrete_map={'华东': '#1E88E5', '华南': '#E53935',
'华北': '#43A047', '西南': '#FB8C00', '华中': '#9C27B0'})
fig.update_layout(
title=dict(x=0.5, font=dict(size=16)),
font=dict(size=13)
)
# 设置动画速度
fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 500
fig.show()
七、导出与分享
7.1 导出为 HTML
import plotly.express as px
import pandas as pd
import numpy as np
# 创建示例图表
np.random.seed(42)
df = pd.DataFrame({
'月份': [f'{i}月' for i in range(1, 13)],
'销售额': [120, 135, 148, 162, 175, 190, 210, 225, 240, 230, 215, 250]
})
fig = px.bar(df, x='月份', y='销售额',
title='月度销售额(可导出为HTML)',
color='销售额',
color_continuous_scale='Blues')
# 导出为 HTML 文件
fig.write_html('月度销售报告.html',
full_html=True, # 包含完整的 HTML 页面
include_plotlyjs=True, # 内嵌 Plotly.js 库
auto_open=False) # 不自动打开浏览器
print("HTML 文件已保存为: 月度销售报告.html")
print("用浏览器打开此文件即可查看交互式图表")
7.2 导出为静态图片
# 导出为 PNG(需要安装 kaleido: pip install kaleido)
# fig.write_image('chart.png', width=1200, height=600, scale=2)
# 导出为 PDF
# fig.write_image('chart.pdf')
# 导出为 SVG(矢量图)
# fig.write_image('chart.svg')
print("提示:导出静态图片需要安装 kaleido 库")
print("安装命令: pip install kaleido")
实战练习
练习一:交互式销售仪表盘
创建一个包含以下内容的交互式销售仪表盘:
- 月度销售趋势折线图(可缩放查看特定时间段)
- 各产品线销售占比饼图(悬停显示金额)
- 区域销售对比柱状图
- 散点图:广告投入与销售额关系,气泡大小代表利润
参考答案:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np
np.random.seed(42)
# 创建仪表盘布局
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('月度销售趋势', '产品线占比', '区域销售对比', '广告投入-销售额矩阵'),
specs=[[{'type': 'xy'}, {'type': 'domain'}],
[{'type': 'xy'}, {'type': 'xy'}]]
)
# 图1:趋势
months = [f'{i}月' for i in range(1, 13)]
sales = [120, 135, 148, 162, 175, 190, 210, 225, 240, 230, 215, 250]
fig.add_trace(go.Scatter(x=months, y=sales, mode='lines+markers',
line=dict(color='#1E88E5', width=3),
marker=dict(size=8), fill='tozeroy',
fillcolor='rgba(30, 136, 229, 0.1)',
name='销售额'),
row=1, col=1)
# 图2:饼图
fig.add_trace(go.Pie(labels=['电子', '服装', '食品', '家居'],
values=[450, 320, 280, 180],
marker=dict(colors=['#1E88E5', '#E53935', '#43A047', '#FB8C00']),
textinfo='label+percent+value', hole=0.4),
row=1, col=2)
# 图3:柱状图
regions = ['华东', '华南', '华北', '西南', '华中', '东北']
reg_sales = [580, 420, 380, 250, 310, 190]
fig.add_trace(go.Bar(x=regions, y=reg_sales,
marker=dict(color=['#1E88E5', '#E53935', '#43A047',
'#FB8C00', '#9C27B0', '#00BCD4'])),
row=2, col=1)
# 图4:散点
n = 50
ad = np.random.uniform(10, 80, n)
rev = 2.5 * ad + np.random.normal(0, 20, n)
profit = np.random.uniform(10, 60, n)
fig.add_trace(go.Scatter(x=ad, y=rev, mode='markers',
marker=dict(size=profit/3, color=profit,
colorscale='Viridis', showscale=True,
colorbar=dict(title='利润'))),
row=2, col=2)
fig.update_layout(
title=dict(text='销售数据交互式仪表盘', x=0.5, font=dict(size=20)),
height=800, showlegend=False, font=dict(size=12)
)
# 导出
fig.write_html('销售仪表盘.html', full_html=True)
print("仪表盘已导出为: 销售仪表盘.html")
fig.show()
练习二:A/B测试结果可视化
模拟一个A/B测试场景:两组用户分别在4周内使用新旧版产品,追踪每周的转化率。创建交互式图表展示结果。
参考答案:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
np.random.seed(42)
fig = make_subplots(
rows=1, cols=2,
subplot_titles=('转化率趋势对比', '累计转化数对比')
)
weeks = [f'第{i}周' for i in range(1, 5)]
# A组(对照组)
conv_a = [3.2, 3.5, 3.8, 4.0]
cum_a = [320, 350, 380, 400]
# B组(实验组)
conv_b = [3.1, 3.4, 4.0, 4.5]
cum_b = [310, 340, 400, 450]
# 转化率折线
fig.add_trace(go.Scatter(x=weeks, y=conv_a, name='A组(旧版)',
mode='lines+markers', line=dict(color='#1E88E5', width=3),
marker=dict(size=10)), row=1, col=1)
fig.add_trace(go.Scatter(x=weeks, y=conv_b, name='B组(新版)',
mode='lines+markers', line=dict(color='#E53935', width=3, dash='dash'),
marker=dict(size=10, symbol='square')), row=1, col=1)
# 累计转化面积
fig.add_trace(go.Scatter(x=weeks, y=cum_a, name='A组累计',
fill='tozeroy', fillcolor='rgba(30,136,229,0.2)',
line=dict(color='#1E88E5')), row=1, col=2)
fig.add_trace(go.Scatter(x=weeks, y=cum_b, name='B组累计',
fill='tozeroy', fillcolor='rgba(229,57,53,0.2)',
line=dict(color='#E53935')), row=1, col=2)
fig.update_layout(
title=dict(text='A/B测试:新旧版产品对比', x=0.5, font=dict(size=18)),
height=450, font=dict(size=13)
)
fig.write_html('AB测试报告.html', full_html=True)
print("报告已导出为: AB测试报告.html")
fig.show()
本节总结
本节我们系统学习了 Plotly 交互式可视化的核心技能:
- Plotly Express:一行代码创建交互式图表,与 Seaborn 用法类似
- 交互式散点图:悬停显示详情、颜色/大小编码多维度信息
- 柱状图与折线图:支持分组、堆叠、误差线等多种变体
- 子图布局:make_subplots 灵活组合多种图表类型
- 高级交互:自定义悬停信息、按钮控件、下拉选择器、动画效果
- 导出分享:HTML 文件可在浏览器中直接打开,无需安装任何软件
- 静态导出:借助 kaleido 库导出 PNG、PDF、SVG 等格式
核心认知:Plotly 的最大价值在于"让图表会说话"。静态图表展示的是"结果",而交互式图表让读者可以"探索"数据本身。在商业汇报中,一个交互式仪表盘往往胜过 20 页静态PPT。
以上就是Python利用Plotly打造可缩放的数据报告的详细内容,更多关于Python Plotly可缩放数据报告的资料请关注脚本之家其它相关文章!
相关文章
Python基于pyCUDA实现GPU加速并行计算功能入门教程
这篇文章主要介绍了Python基于pyCUDA实现GPU加速并行计算功能,结合实例形式分析了Python使用pyCUDA进行GPU加速并行计算的原理与相关实现操作技巧,需要的朋友可以参考下2018-06-06
PyCharm上安装Package的实现(以pandas为例)
这篇文章主要介绍了PyCharm上安装Package的实现(以pandas为例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-09-09


最新评论