AI真好玩系列-Agent Skill深度调研⑯data-visualization | AI数据可视化报表神器
@[TOC]( AI真好玩系列-Agent Skill深度调研⑯data-visualization | AI数据可视化报表神器)
开头碎碎念
宝宝们又见面啦~👋 今天给大家带来一个让我疯狂心动的 Agent Skill——data-visualization!
你有没有经历过这种绝望时刻:老板说"给我做一个数据看板",然后你打开 matplotlib 文档,开始查怎么画折线图、怎么调颜色、怎么加图例、怎么设置中文字体...一整天过去了,图是画出来了,但丑得你自己都不想看 😅
更惨的是,你辛辛苦苦画了一堆静态图,老板说"能不能做成那种可以交互的?鼠标悬停显示数据的?"你看了看 ECharts 文档,再看了看 Plotly 的 API,内心OS:我直接辞职行不行?😭
还有更离谱的——数据分析师每天80%的时间都在调图表样式,只有20%的时间在真正分析数据。这不是本末倒置吗?数据可视化的目的是让数据说话,不是让你跟 CSS 死磕啊!😤
现在有了 data-visualization Skill,AI Agent 可以直接帮你从原始数据生成各种精美图表——你说"画一个销售趋势折线图",它就给你画;你说"加个对比柱状图",它就加;你说"做成可交互的",它直接给你输出 HTML!这才是数据可视化该有的样子!🎨
废话不多说,让我带你从原理到实战,彻底搞懂这个 AI 数据可视化报表神器~ 🚀
🌟 项目简介 | Project Introduction
data-visualization 是 Agent 生态中最热门的数据可视化 Skill,它让 AI Agent 能够根据自然语言指令自动生成各种类型的图表和可视化报表。无论是简单的折线图还是复杂的交互式仪表盘,只需一句话描述,AI 就能帮你从原始数据到精美图表一站式搞定。
- 核心定位:AI Agent 的数据可视化能力引擎
- 底层引擎:matplotlib / seaborn / plotly / ECharts / D3.js
- 图表类型:折线图、柱状图、饼图、散点图、热力图、地图、雷达图、漏斗图、桑基图等30+种
- 交互支持:静态图表(PNG/SVG)+ 交互式图表(HTML/Widget)
- 适用场景:数据分析报告、商业智能看板、科学论文配图、实时监控大屏
核心特性一览
| 特性 | 说明 |
|---|---|
| 多图表类型 | 支持30+种图表类型,覆盖主流可视化需求 |
| 自然语言驱动 | 用中文描述需求,自动生成对应图表 |
| 交互式图表 | 支持缩放、悬停、筛选等交互操作 |
| 样式定制 | 支持主题、配色、字体、布局等深度定制 |
| 多数据源 | 支持 CSV/Excel/JSON/数据库/API 等数据源 |
| 自动布局 | 智能调整图表布局,避免标签重叠 |
| 中文支持 | 完美支持中文显示和排版 |
| 导出格式 | PNG/SVG/PDF/HTML/交互式Widget |
| 实时更新 | 支持数据流式输入和图表实时刷新 |
| 响应式设计 | 图表自适应不同屏幕尺寸 |
🤔 为什么需要 data-visualization? | Why data-visualization?
痛点场景还原
痛点1:手动写图表代码太痛苦
你:我需要画一个销售趋势图
内心OS:简单,matplotlib 几行代码搞定...
5分钟后:为什么中文显示成方块了?
10分钟后:为什么图例把数据挡住了?
20分钟后:为什么颜色这么丑?
30分钟后:为什么X轴标签重叠了?
1小时后:为什么保存的图片这么模糊?
2小时后:算了,我直接用Excel画吧...😅
痛点2:交互式图表门槛太高
你:老板要一个可以交互的数据看板
你(查ECharts文档):配置项这么多??
你:option = { title: {...}, tooltip: {...}, legend: {...},
xAxis: {...}, yAxis: {...}, series: [{...}] }
你:这还没写完呢,已经100行了...
你:而且还要处理数据格式转换、响应式布局、主题切换...
你:我只是一个后端开发啊!!!😭
痛点3:数据到图表的转换效率低下
数据分析师的一天:
8:00 收到需求:做一份月度销售报告
8:30 从数据库导出数据
9:00 用 pandas 清洗数据
9:30 开始画图...折线图、柱状图、饼图...
11:00 调样式...颜色、字体、间距...
12:00 午饭
13:00 继续调样式...
14:00 老板:能不能加一个同比环比对比?
14:30 重新画...
15:30 老板:能不能做成可交互的?
16:00 开始学 ECharts...
18:00 终于搞定了...明天又要做周报 😭
痛点4:图表风格不统一
你:把这三个人的图表合并到一份报告里
同事A的图:默认蓝底白字,matplotlib风格
同事B的图:粉色系,seaborn风格
同事C的图:深色主题,plotly风格
你:这放在一起...像什么样子?😅
你:统一风格?那得三个人一起改...
你:算了,我自己重新画吧...
对比表格
| 问题 | 手动编码 | BI工具 | data-visualization Skill |
|---|---|---|---|
| 学习成本 | 高(需学API) | 中(需学操作) | 低(自然语言) ✔️ |
| 开发效率 | 低(小时级) | 中(分钟级) | 高(秒级) ✔️ |
| 图表类型 | 受限于库 | 受限于模板 | 30+种全覆盖 ✔️ |
| 交互能力 | 需额外开发 | 内置但有限 | 自动生成 ✔️ |
| 样式定制 | 灵活但繁琐 | 受限于配置 | 智能推荐 ✔️ |
| 风格统一 | 难以保证 | 模板统一 | 主题系统 ✔️ |
| 数据适配 | 手动转换 | 需建模 | 自动推断 ✔️ |
| 迭代速度 | 慢(改代码) | 中(改配置) | 快(改描述) ✔️ |
一句话总结:**data-visualization 让数据可视化从"手工作坊"变成"智能工厂"!**🏭
📌 前提条件 | Prerequisites
- Python 环境:Python 3.8+,建议使用虚拟环境
- 基础库安装:matplotlib、seaborn、plotly、pandas、numpy
- 数据准备:CSV/Excel/JSON 格式的数据文件
- Agent 框架:LangChain / CrewAI / AutoGen 等任一框架
- 基础概念:了解常见图表类型及其适用场景
🚀 核心技术栈 | Core Technologies
| 技术 | 版本 | 用途 | 链接 |
|---|---|---|---|
| matplotlib | 3.8+ | 基础绑图引擎 | matplotlib.org |
| seaborn | 0.13+ | 统计可视化 | seaborn.pydata.org |
| plotly | 5.18+ | 交互式可视化 | plotly.com/python |
| pyecharts | 2.0+ | ECharts Python封装 | pyecharts.org |
| pandas | 2.1+ | 数据处理 | pandas.pydata.org |
| numpy | 1.24+ | 数值计算 | numpy.org |
| folium | 0.15+ | 地理可视化 | python-visualization.github.io/folium |
| altair | 5.2+ | 声明式可视化 | altair-viz.github.io |
| bokeh | 3.3+ | 浏览器端可视化 | bokeh.org |
🧩 核心原理详解 | Core Principles
1. 可视化流程 | Visualization Pipeline
data-visualization Skill 的核心是一个完整的可视化管道,从自然语言到最终图表,经历数据解析、图表推断、代码生成、渲染输出四个阶段:
用户自然语言描述
"画一个销售趋势折线图,按月分组"
↓
【阶段1:意图解析】
├── 提取图表类型:折线图 (line chart)
├── 提取数据维度:时间(X轴)、销售额(Y轴)
├── 提取分组方式:按月分组
└── 提取样式偏好:默认/用户指定
↓
【阶段2:数据适配】
├── 读取数据源(CSV/Excel/JSON/DB)
├── 推断列类型(数值/日期/分类)
├── 数据聚合(按月分组求和)
├── 数据清洗(缺失值/异常值)
└── 生成绘图数据帧
↓
【阶段3:代码生成】
├── 选择渲染引擎(matplotlib/plotly/echarts)
├── 生成图表配置代码
├── 应用样式主题
├── 设置中文支持
└── 配置交互行为
↓
【阶段4:渲染输出】
├── 执行代码生成图表
├── 静态输出:PNG/SVG/PDF
├── 交互输出:HTML/Widget
└── 返回图表文件/嵌入代码
# 可视化管道核心实现(简化版)
from dataclasses import dataclass
from typing import Optional, List, Dict, Any
from enum import Enum
class ChartType(Enum):
"""支持的图表类型枚举"""
LINE = "line" # 折线图
BAR = "bar" # 柱状图
PIE = "pie" # 饼图
SCATTER = "scatter" # 散点图
HEATMAP = "heatmap" # 热力图
AREA = "area" # 面积图
RADAR = "radar" # 雷达图
FUNNEL = "funnel" # 漏斗图
SANKEY = "sankey" # 桑基图
TREEMAP = "treemap" # 矩形树图
MAP = "map" # 地图
BOX = "box" # 箱线图
VIOLIN = "violin" # 小提琴图
HISTOGRAM = "histogram" # 直方图
CANDLESTICK = "candlestick" # K线图
class RenderEngine(Enum):
"""渲染引擎枚举"""
MATPLOTLIB = "matplotlib" # 静态图表
SEABORN = "seaborn" # 统计图表
PLOTLY = "plotly" # 交互式图表
ECHARTS = "pyecharts" # ECharts图表
ALTAIR = "altair" # 声明式图表
@dataclass
class VisualizationRequest:
"""可视化请求——用户意图的结构化表示"""
chart_type: ChartType # 图表类型
data_source: str # 数据源路径
x_column: Optional[str] = None # X轴列名
y_column: Optional[str] = None # Y轴列名
group_by: Optional[str] = None # 分组列名
title: Optional[str] = None # 图表标题
theme: str = "default" # 主题风格
engine: RenderEngine = RenderEngine.MATPLOTLIB # 渲染引擎
interactive: bool = False # 是否交互式
output_format: str = "png" # 输出格式
width: int = 1200 # 宽度
height: int = 800 # 高度
style_options: Dict[str, Any] = None # 额外样式选项
class VisualizationPipeline:
"""可视化管道——从自然语言到图表的完整流程"""
def __init__(self):
self.intent_parser = IntentParser() # 意图解析器
self.data_adapter = DataAdapter() # 数据适配器
self.code_generator = CodeGenerator() # 代码生成器
self.renderer = ChartRenderer() # 图表渲染器
def visualize(self, user_query: str, data_source: str) -> str:
"""
主入口:从自然语言生成图表
返回图表文件路径或HTML代码
"""
# 阶段1:意图解析——从自然语言提取可视化参数
request = self.intent_parser.parse(user_query, data_source)
# 阶段2:数据适配——读取并预处理数据
plot_data = self.data_adapter.adapt(request)
# 阶段3:代码生成——生成绘图代码
code = self.code_generator.generate(request, plot_data)
# 阶段4:渲染输出——执行代码生成图表
output = self.renderer.render(code, request.output_format)
return output
2. 图表类型详解 | Chart Types Deep Dive
data-visualization Skill 支持丰富的图表类型,每种类型都有其最佳适用场景:
# 图表类型选择指南
CHART_TYPE_GUIDE = {
# ===== 趋势类图表 =====
"line": {
"name": "折线图",
"best_for": "展示数据随时间的变化趋势",
"example": "月度销售额趋势、股票价格走势",
"data_requirement": "至少一个时间维度 + 一个数值维度",
"code": """
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 准备数据——模拟12个月的销售数据
months = ['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月']
sales_2025 = [120, 135, 148, 162, 155, 178, 192, 205, 198, 215, 228, 245]
sales_2026 = [145, 158, 172, 185, 180, 205, 218, 235, 225, 248, 262, 280]
# 创建折线图
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制两条折线
ax.plot(months, sales_2025, marker='o', linewidth=2.5,
label='2025年', color='#4ECDC4', markersize=8)
ax.plot(months, sales_2026, marker='s', linewidth=2.5,
label='2026年', color='#FF6B6B', markersize=8)
# 填充两条线之间的区域
ax.fill_between(months, sales_2025, sales_2026,
alpha=0.15, color='#FF6B6B')
# 添加数据标签(仅在首尾和极值点)
ax.annotate(f'{sales_2025[0]}万', xy=(0, sales_2025[0]),
textcoords="offset points", xytext=(0, 12), ha='center',
fontsize=9, color='#4ECDC4')
ax.annotate(f'{sales_2025[-1]}万', xy=(11, sales_2025[-1]),
textcoords="offset points", xytext=(0, 12), ha='center',
fontsize=9, color='#4ECDC4')
ax.annotate(f'{sales_2026[-1]}万', xy=(11, sales_2026[-1]),
textcoords="offset points", xytext=(0, 12), ha='center',
fontsize=9, color='#FF6B6B', fontweight='bold')
# 样式美化
ax.set_title('月度销售额趋势对比', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('月份', fontsize=13)
ax.set_ylabel('销售额(万元)', fontsize=13)
ax.legend(fontsize=12, loc='upper left')
ax.grid(True, alpha=0.3, linestyle='--')
ax.set_ylim(100, 300)
# 去掉上方和右方的边框
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.savefig('sales_trend.png', dpi=150, bbox_inches='tight')
print("折线图已生成!")
""",
},
# ===== 对比类图表 =====
"bar": {
"name": "柱状图",
"best_for": "比较不同类别之间的数值差异",
"example": "各产品销售额对比、各地区业绩排名",
"data_requirement": "一个分类维度 + 一个数值维度",
"code": """
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 准备数据——各产品线季度销售额
products = ['智能手机', '笔记本电脑', '平板电脑', '智能手表', '耳机', '配件']
q1 = [4500, 3200, 1800, 1200, 900, 600]
q2 = [4800, 3500, 2100, 1500, 1100, 750]
q3 = [5200, 3800, 1900, 1800, 1300, 850]
q4 = [5800, 4200, 2400, 2200, 1600, 950]
# 创建分组柱状图
fig, ax = plt.subplots(figsize=(14, 7))
x = np.arange(len(products)) # X轴位置
width = 0.2 # 柱子宽度
# 绘制四个季度的柱子
colors = ['#4ECDC4', '#45B7D1', '#FFA07A', '#FF6B6B']
bars1 = ax.bar(x - 1.5*width, q1, width, label='Q1', color=colors[0], edgecolor='white')
bars2 = ax.bar(x - 0.5*width, q2, width, label='Q2', color=colors[1], edgecolor='white')
bars3 = ax.bar(x + 0.5*width, q3, width, label='Q3', color=colors[2], edgecolor='white')
bars4 = ax.bar(x + 1.5*width, q4, width, label='Q4', color=colors[3], edgecolor='white')
# 在柱子顶部添加数值标签
for bars in [bars1, bars2, bars3, bars4]:
for bar in bars:
height = bar.get_height()
ax.annotate(f'{int(height)}',
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3), textcoords="offset points",
ha='center', va='bottom', fontsize=7)
# 样式美化
ax.set_title('各产品线季度销售额对比(万元)', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('产品线', fontsize=13)
ax.set_ylabel('销售额(万元)', fontsize=13)
ax.set_xticks(x)
ax.set_xticklabels(products, fontsize=11)
ax.legend(fontsize=11, ncol=4, loc='upper center',
bbox_to_anchor=(0.5, -0.08))
ax.grid(True, axis='y', alpha=0.3, linestyle='--')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.savefig('sales_bar.png', dpi=150, bbox_inches='tight')
print("柱状图已生成!")
""",
},
# ===== 占比类图表 =====
"pie": {
"name": "饼图",
"best_for": "展示各部分占整体的比例关系",
"example": "市场份额分布、支出结构占比",
"data_requirement": "一个分类维度 + 一个数值维度(各部分之和为100%)",
"code": """
import matplotlib.pyplot as plt
import matplotlib
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 准备数据——2026年Q1市场份额
labels = ['华为', '苹果', '小米', 'OPPO', 'vivo', '其他']
sizes = [28.5, 22.3, 18.7, 12.4, 10.8, 7.3]
explode = (0.06, 0, 0, 0, 0, 0) # 突出显示华为
# 配色方案——渐变色系
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD']
# 创建饼图
fig, ax = plt.subplots(figsize=(10, 8))
wedges, texts, autotexts = ax.pie(
sizes,
explode=explode,
labels=labels,
colors=colors,
autopct='%1.1f%%', # 显示百分比
startangle=90, # 起始角度
pctdistance=0.75, # 百分比标签距离
shadow=False, # 不显示阴影
wedgeprops=dict(width=0.6, edgecolor='white', linewidth=2) # 环形图效果
)
# 美化文字样式
for text in texts:
text.set_fontsize(12)
text.set_fontweight('bold')
for autotext in autotexts:
autotext.set_fontsize(10)
autotext.set_color('white')
autotext.set_fontweight('bold')
# 添加中心文字
ax.text(0, 0, '2026 Q1\\n手机市场', ha='center', va='center',
fontsize=14, fontweight='bold', color='#333333')
ax.set_title('2026年Q1智能手机市场份额', fontsize=18,
fontweight='bold', pad=25)
plt.tight_layout()
plt.savefig('market_pie.png', dpi=150, bbox_inches='tight')
print("饼图已生成!")
""",
},
# ===== 关系类图表 =====
"scatter": {
"name": "散点图",
"best_for": "展示两个变量之间的关系和相关性",
"example": "广告投入与销售额的关系、身高体重分布",
"data_requirement": "两个数值维度(X和Y)",
"code": """
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 生成模拟数据——广告投入 vs 销售额
np.random.seed(42)
n = 200
ad_spend = np.random.uniform(5, 100, n) # 广告投入(万元)
# 销售额与广告投入正相关,加入随机噪声
sales = ad_spend * 2.5 + np.random.normal(0, 30, n) + 50
sales = np.clip(sales, 20, 350) # 限制范围
# 按广告投入大小分组,用于颜色映射
categories = np.where(ad_spend < 30, '低投入',
np.where(ad_spend < 60, '中投入', '高投入'))
# 创建散点图
fig, ax = plt.subplots(figsize=(12, 8))
# 按分组绘制散点
color_map = {'低投入': '#45B7D1', '中投入': '#FFA07A', '高投入': '#FF6B6B'}
for cat in ['低投入', '中投入', '高投入']:
mask = categories == cat
ax.scatter(ad_spend[mask], sales[mask],
c=color_map[cat], label=cat,
alpha=0.7, s=80, edgecolors='white', linewidth=0.5)
# 添加趋势线
z = np.polyfit(ad_spend, sales, 1) # 线性拟合
p = np.poly1d(z)
x_line = np.linspace(5, 100, 100)
ax.plot(x_line, p(x_line), '--', color='#333333', linewidth=2,
alpha=0.7, label=f'趋势线 (y={z[0]:.1f}x+{z[1]:.1f})')
# 计算相关系数
correlation = np.corrcoef(ad_spend, sales)[0, 1]
ax.text(0.05, 0.95, f'相关系数 r = {correlation:.3f}',
transform=ax.transAxes, fontsize=12,
verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# 样式美化
ax.set_title('广告投入与销售额关系分析', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('广告投入(万元)', fontsize=13)
ax.set_ylabel('销售额(万元)', fontsize=13)
ax.legend(fontsize=11, loc='lower right')
ax.grid(True, alpha=0.3, linestyle='--')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.savefig('scatter_ad_sales.png', dpi=150, bbox_inches='tight')
print("散点图已生成!")
""",
},
# ===== 矩阵类图表 =====
"heatmap": {
"name": "热力图",
"best_for": "展示矩阵数据的分布和模式",
"example": "特征相关性矩阵、区域-时间销售分布",
"data_requirement": "二维矩阵数据(行×列)",
"code": """
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
import numpy as np
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 准备数据——各城市各月份的平均气温
cities = ['北京', '上海', '广州', '成都', '哈尔滨', '昆明']
months = ['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月']
# 各城市月均气温(摄氏度)
temp_data = np.array([
[-3.1, 0.2, 7.5, 15.2, 21.8, 26.1, 27.8, 26.5, 21.2, 14.1, 5.3, -1.2], # 北京
[4.8, 5.9, 10.0, 15.7, 21.2, 25.0, 28.8, 28.5, 24.5, 19.2, 13.0, 6.8], # 上海
[14.1, 15.2, 18.5, 22.8, 26.2, 28.1, 28.9, 28.6, 27.2, 24.0, 19.5, 15.2], # 广州
[6.2, 8.5, 13.2, 18.1, 22.5, 24.8, 26.1, 25.8, 22.1, 17.2, 12.0, 7.5], # 成都
[-18.5, -13.2, -2.8, 8.5, 16.8, 22.1, 24.5, 22.8, 15.2, 5.8, -5.5, -15.2], # 哈尔滨
[9.2, 11.5, 15.0, 18.2, 20.5, 20.8, 20.2, 19.8, 18.5, 15.8, 12.0, 9.5], # 昆明
])
# 创建热力图
fig, ax = plt.subplots(figsize=(14, 6))
# 使用seaborn绘制热力图
sns.heatmap(temp_data,
annot=True, # 显示数值
fmt='.1f', # 数值格式:1位小数
cmap='RdYlBu_r', # 红黄蓝反转色系(红=热,蓝=冷)
center=15, # 色彩中心值
linewidths=0.5, # 格子间距
linecolor='white', # 格子线颜色
xticklabels=months,
yticklabels=cities,
cbar_kws={'label': '温度 (°C)', 'shrink': 0.8},
ax=ax)
# 样式美化
ax.set_title('中国主要城市月均气温热力图(°C)', fontsize=18,
fontweight='bold', pad=20)
ax.set_xlabel('月份', fontsize=13)
ax.set_ylabel('城市', fontsize=13)
# 调整标签
ax.set_xticklabels(ax.get_xticklabels(), rotation=0, ha='center')
ax.set_yticklabels(ax.get_yticklabels(), rotation=0)
plt.tight_layout()
plt.savefig('temp_heatmap.png', dpi=150, bbox_inches='tight')
print("热力图已生成!")
""",
},
}
# 图表类型选择决策树
def recommend_chart_type(
has_time_dimension: bool,
has_category_dimension: bool,
num_numeric_columns: int,
want_comparison: bool,
want_proportion: bool,
want_relationship: bool,
want_distribution: bool,
) -> str:
"""
根据数据特征推荐最合适的图表类型
这是 data-visualization Skill 内部的智能推荐逻辑
"""
if want_proportion and has_category_dimension:
return "pie" # 饼图:展示占比
elif want_relationship and num_numeric_columns >= 2:
return "scatter" # 散点图:展示关系
elif want_distribution:
return "histogram" # 直方图:展示分布
elif has_time_dimension and want_comparison:
return "line" # 折线图:展示趋势
elif has_category_dimension and want_comparison:
return "bar" # 柱状图:展示对比
elif has_time_dimension:
return "line" # 默认:时间维度用折线图
elif has_category_dimension:
return "bar" # 默认:分类维度用柱状图
else:
return "scatter" # 默认:纯数值用散点图
# 使用示例
print(recommend_chart_type(
has_time_dimension=True,
has_category_dimension=False,
num_numeric_columns=1,
want_comparison=True,
want_proportion=False,
want_relationship=False,
want_distribution=False,
))
# 输出: line(推荐折线图展示时间趋势)
3. 样式定制系统 | Style Customization
data-visualization Skill 提供了强大的样式定制能力,从全局主题到单个元素都可以精细控制:
# 样式定制系统完整实现
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
@dataclass
class ChartTheme:
"""图表主题——统一控制全局样式"""
# ===== 颜色系统 =====
primary_colors: List[str] = field(default_factory=lambda: [
'#4ECDC4', '#FF6B6B', '#45B7D1', '#96CEB4',
'#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F',
])
background_color: str = '#FFFFFF'
text_color: str = '#333333'
grid_color: str = '#E0E0E0'
# ===== 字体系统 =====
title_font: Dict = field(default_factory=lambda: {
'family': 'Arial Unicode MS, SimHei, sans-serif',
'size': 18,
'weight': 'bold',
'color': '#333333',
})
label_font: Dict = field(default_factory=lambda: {
'family': 'Arial Unicode MS, SimHei, sans-serif',
'size': 13,
'weight': 'normal',
'color': '#555555',
})
tick_font: Dict = field(default_factory=lambda: {
'family': 'Arial Unicode MS, SimHei, sans-serif',
'size': 11,
'weight': 'normal',
'color': '#666666',
})
# ===== 布局系统 =====
figure_size: Tuple[int, int] = (12, 7)
dpi: int = 150
margin: Dict = field(default_factory=lambda: {
'top': 0.92, 'bottom': 0.08,
'left': 0.08, 'right': 0.95,
})
grid_visible: bool = True
grid_alpha: float = 0.3
grid_style: str = '--'
# ===== 边框系统 =====
show_top_spine: bool = False
show_right_spine: bool = False
spine_color: str = '#CCCCCC'
# ===== 预置主题库 =====
THEME_DARK = ChartTheme(
primary_colors=['#00D4FF', '#FF6B9D', '#C084FC', '#34D399',
'#FBBF24', '#F87171', '#A78BFA', '#6EE7B7'],
background_color='#1A1A2E',
text_color='#E0E0E0',
grid_color='#333355',
title_font={'family': 'sans-serif', 'size': 18, 'weight': 'bold', 'color': '#FFFFFF'},
label_font={'family': 'sans-serif', 'size': 13, 'weight': 'normal', 'color': '#BBBBBB'},
tick_font={'family': 'sans-serif', 'size': 11, 'weight': 'normal', 'color': '#999999'},
grid_color='#333355',
spine_color='#444466',
)
THEME_MINIMAL = ChartTheme(
primary_colors=['#2196F3', '#FF5722', '#4CAF50', '#FFC107',
'#9C27B0', '#00BCD4', '#E91E63', '#8BC34A'],
background_color='#FAFAFA',
text_color='#212121',
grid_visible=False,
show_top_spine=False,
show_right_spine=False,
)
THEME_BUSINESS = ChartTheme(
primary_colors=['#1B4F72', '#2E86C1', '#5DADE2', '#85C1E9',
'#AED6F1', '#D4E6F1', '#EAF2F8', '#F0F8FF'],
background_color='#FFFFFF',
text_color='#1B2631',
grid_alpha=0.15,
grid_style='-',
)
# ===== 主题应用示例 =====
def apply_theme(theme: ChartTheme):
"""将主题应用到 matplotlib 全局配置"""
import matplotlib
import matplotlib.pyplot as plt
# 设置中文字体
font_families = theme.title_font.get('family', 'sans-serif')
matplotlib.rcParams['font.sans-serif'] = font_families.split(', ')
matplotlib.rcParams['axes.unicode_minus'] = False
# 应用颜色
matplotlib.rcParams['figure.facecolor'] = theme.background_color
matplotlib.rcParams['axes.facecolor'] = theme.background_color
matplotlib.rcParams['text.color'] = theme.text_color
matplotlib.rcParams['axes.labelcolor'] = theme.label_font.get('color', theme.text_color)
matplotlib.rcParams['xtick.color'] = theme.tick_font.get('color', theme.text_color)
matplotlib.rcParams['ytick.color'] = theme.tick_font.get('color', theme.text_color)
# 应用字体大小
matplotlib.rcParams['axes.titlesize'] = theme.title_font.get('size', 18)
matplotlib.rcParams['axes.labelsize'] = theme.label_font.get('size', 13)
matplotlib.rcParams['xtick.labelsize'] = theme.tick_font.get('size', 11)
matplotlib.rcParams['ytick.labelsize'] = theme.tick_font.get('size', 11)
# 应用网格
matplotlib.rcParams['grid.alpha'] = theme.grid_alpha
matplotlib.rcParams['grid.linestyle'] = theme.grid_style
matplotlib.rcParams['grid.color'] = theme.grid_color
print(f"主题已应用!背景色: {theme.background_color}")
# 使用示例:应用暗色主题
apply_theme(THEME_DARK)
# 样式定制实战——从默认到精美的完整过程
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# ===== 1. 默认样式(丑) =====
fig, ax = plt.subplots()
ax.bar(['A', 'B', 'C', 'D'], [25, 40, 30, 55])
ax.set_title('默认样式')
plt.savefig('default_style.png', dpi=100)
plt.close()
# 输出:蓝色柱子,灰色背景,无美感
# ===== 2. 基础美化 =====
fig, ax = plt.subplots(figsize=(10, 6))
colors = ['#4ECDC4', '#45B7D1', '#FFA07A', '#FF6B6B']
bars = ax.bar(['产品A', '产品B', '产品C', '产品D'],
[25, 40, 30, 55], color=colors, edgecolor='white', width=0.6)
ax.set_title('基础美化', fontsize=16, fontweight='bold')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.grid(axis='y', alpha=0.3, linestyle='--')
plt.savefig('basic_style.png', dpi=150)
plt.close()
# ===== 3. 高级定制 =====
fig, ax = plt.subplots(figsize=(12, 7))
# 渐变色柱子
categories = ['智能手机', '笔记本电脑', '平板电脑', '智能手表']
values = [2580, 3420, 1890, 1250]
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
bars = ax.bar(categories, values, color=colors,
edgecolor='white', linewidth=2, width=0.55, zorder=3)
# 添加渐变效果(通过叠加半透明矩形模拟)
for bar, color in zip(bars, colors):
bar.set_alpha(0.9)
# 数值标签
for bar in bars:
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2., height + 50,
f'{int(height)}万',
ha='center', va='bottom', fontsize=13,
fontweight='bold', color='#333333')
# 副标题
ax.text(0.5, 1.02, '2026年Q1各品类销售额',
transform=ax.transAxes, ha='center',
fontsize=11, color='#888888', style='italic')
# 样式
ax.set_title('产品销售额排行', fontsize=20, fontweight='bold',
pad=30, color='#1A1A2E')
ax.set_ylabel('销售额(万元)', fontsize=13, color='#555555')
ax.set_ylim(0, 4200)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color('#CCCCCC')
ax.spines['bottom'].set_color('#CCCCCC')
ax.grid(axis='y', alpha=0.2, linestyle='--', zorder=0)
ax.tick_params(axis='x', labelsize=12, colors='#333333')
ax.tick_params(axis='y', labelsize=10, colors='#888888')
# 添加数据来源注释
ax.text(1.0, -0.12, '数据来源:内部销售系统 | 更新时间:2026-03-31',
transform=ax.transAxes, ha='right', fontsize=8, color='#AAAAAA')
plt.tight_layout()
plt.savefig('advanced_style.png', dpi=150, bbox_inches='tight')
plt.close()
print("高级定制图表已生成!")
4. 交互式图表 | Interactive Charts
data-visualization Skill 最强大的能力之一是生成交互式图表,用户可以缩放、悬停、筛选、联动:
# ===== Plotly 交互式图表 =====
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
# ===== 交互式折线图 =====
# 准备数据
np.random.seed(42)
dates = pd.date_range('2025-01-01', '2026-03-31', freq='D')
df = pd.DataFrame({
'date': dates,
'sales': np.cumsum(np.random.randn(len(dates)) * 50 + 100),
'region': np.random.choice(['华东', '华南', '华北', '西南'], len(dates)),
})
# 按区域聚合
daily_sales = df.groupby(['date', 'region'])['sales'].sum().reset_index()
# 创建交互式折线图
fig = px.line(
daily_sales,
x='date',
y='sales',
color='region', # 按区域着色
title='各区域日销售额趋势(交互式)',
labels={'date': '日期', 'sales': '销售额', 'region': '区域'},
color_discrete_sequence=['#4ECDC4', '#FF6B6B', '#45B7D1', '#96CEB4'],
)
# 添加范围选择器和滑块
fig.update_xaxes(
rangeslider_visible=True, # 显示范围滑块
rangeselector=dict(
buttons=list([
dict(count=7, label="1周", step="day", stepmode="backward"),
dict(count=1, label="1月", step="month", stepmode="backward"),
dict(count=3, label="3月", step="month", stepmode="backward"),
dict(count=6, label="6月", step="month", stepmode="backward"),
dict(step="all", label="全部"),
])
)
)
# 配置悬停信息
fig.update_traces(
hovertemplate='<b>%{fullData.name}</b><br>' +
'日期: %{x|%Y-%m-%d}<br>' +
'销售额: %{y:,.0f}元<extra></extra>',
line_width=2.5,
)
# 全局布局
fig.update_layout(
template='plotly_white',
hovermode='x unified', # 统一悬停模式
legend=dict(
orientation="h",
yanchor="bottom", y=1.02,
xanchor="right", x=1,
),
font=dict(family="Arial, SimHei, sans-serif", size=12),
)
# 导出为HTML
fig.write_html("interactive_line.html")
print("交互式折线图已生成!打开 interactive_line.html 查看")
# ===== 交互式散点图(带动画) =====
# 使用内置的gapminder数据集
df_gap = px.data.gapminder()
fig = px.scatter(
df_gap.query("year >= 1997"),
x="gdpPercap",
y="lifeExp",
size="pop",
color="continent",
animation_frame="year", # 动画帧:年份
animation_group="country",
hover_name="country",
log_x=True,
size_max=55,
range_x=[100, 100000],
range_y=[25, 90],
title='全球各国GDP与寿命关系演变',
labels={
'gdpPercap': '人均GDP(对数)',
'lifeExp': '预期寿命(岁)',
'continent': '大洲',
},
color_discrete_sequence=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'],
)
fig.update_layout(template='plotly_white')
fig.write_html("animated_scatter.html")
print("动画散点图已生成!")
# ===== 交互式仪表盘 =====
from plotly.subplots import make_subplots
# 创建2x2子图仪表盘
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('销售趋势', '品类占比', '区域对比', '月度环比'),
specs=[
[{"type": "scatter"}, {"type": "pie"}],
[{"type": "bar"}, {"type": "scatter"}],
],
)
# 子图1:销售趋势
months = ['1月', '2月', '3月', '4月', '5月', '6月']
fig.add_trace(
go.Scatter(x=months, y=[120, 135, 148, 162, 155, 178],
mode='lines+markers', name='2025年',
line=dict(color='#4ECDC4', width=2.5)),
row=1, col=1,
)
fig.add_trace(
go.Scatter(x=months, y=[145, 158, 172, 185, 180, 205],
mode='lines+markers', name='2026年',
line=dict(color='#FF6B6B', width=2.5)),
row=1, col=1,
)
# 子图2:品类占比
fig.add_trace(
go.Pie(labels=['手机', '电脑', '平板', '配件'],
values=[45, 25, 18, 12],
marker=dict(colors=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4'])),
row=1, col=2,
)
# 子图3:区域对比
fig.add_trace(
go.Bar(x=['华东', '华南', '华北', '西南'],
y=[2800, 2200, 1900, 1500],
marker=dict(color=['#4ECDC4', '#45B7D1', '#FFA07A', '#96CEB4'])),
row=2, col=1,
)
# 子图4:月度环比
mom_rate = [5.2, 8.3, 6.1, -2.5, 12.8, 9.7]
fig.add_trace(
go.Bar(x=months, y=mom_rate,
marker=dict(color=['#4ECDC4' if v >= 0 else '#FF6B6B' for v in mom_rate])),
row=2, col=2,
)
# 全局布局
fig.update_layout(
title_text='销售数据仪表盘(交互式)',
title_font_size=20,
showlegend=True,
template='plotly_white',
height=800,
)
fig.write_html("dashboard.html")
print("交互式仪表盘已生成!")
# ===== ECharts 交互式图表(通过pyecharts) =====
from pyecharts import options as opts
from pyecharts.charts import Bar, Line, Pie, Map, HeatMap
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType
# ===== ECharts 柱状图 =====
# 准备数据
products = ['智能手机', '笔记本电脑', '平板电脑', '智能手表', '耳机', '配件']
sales_2025 = [4500, 3200, 1800, 1200, 900, 600]
sales_2026 = [5800, 4200, 2400, 2200, 1600, 950]
# 创建柱状图
bar = (
Bar(init_opts=opts.InitOpts(
theme=ThemeType.LIGHT, # 使用浅色主题
width="1200px",
height="600px",
))
.add_xaxis(products)
.add_yaxis("2025年", sales_2025,
color="#4ECDC4",
itemstyle_opts=opts.ItemStyleOpts(
border_radius=[5, 5, 0, 0] # 圆角柱子
))
.add_yaxis("2026年", sales_2026,
color="#FF6B6B",
itemstyle_opts=opts.ItemStyleOpts(
border_radius=[5, 5, 0, 0]
))
.set_global_opts(
title_opts=opts.TitleOpts(
title="各产品线销售额对比",
subtitle="单位:万元",
pos_left="center",
),
tooltip_opts=opts.TooltipOpts(
trigger="axis",
axis_pointer_type="shadow", # 阴影指示器
),
legend_opts=opts.LegendOpts(pos_top="8%"),
xaxis_opts=opts.AxisOpts(
axislabel_opts=opts.LabelOpts(rotate=0),
),
yaxis_opts=opts.AxisOpts(
name="销售额(万元)",
splitline_opts=opts.SplitLineOpts(is_show=True),
),
)
)
bar.render("echarts_bar.html")
print("ECharts柱状图已生成!")
# ===== ECharts 中国地图 =====
# 各省份销售额数据
province_data = [
("广东省", 8950), ("江苏省", 7820), ("浙江省", 6980),
("山东省", 5650), ("河南省", 4320), ("四川省", 4180),
("湖北省", 3850), ("湖南省", 3620), ("福建省", 3580),
("安徽省", 3150), ("河北省", 2980), ("北京市", 2850),
("上海市", 2720), ("辽宁省", 2480), ("陕西省", 2250),
("重庆市", 2080), ("江西省", 1850), ("云南省", 1720),
("广西壮族自治区", 1580), ("山西省", 1420),
]
# 创建地图
map_chart = (
Map(init_opts=opts.InitOpts(
theme=ThemeType.LIGHT,
width="1000px",
height="700px",
))
.add(
series_name="销售额",
data_pair=province_data,
maptype="china", # 中国地图
is_map_symbol_show=False, # 不显示标记点
label_opts=opts.LabelOpts(is_show=False), # 不显示省份名
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="全国各省份销售额分布",
subtitle="2026年Q1 | 单位:万元",
pos_left="center",
),
visualmap_opts=opts.VisualMapOpts(
min_=1000,
max_=9000,
is_piecewise=True, # 分段显示
pieces=[
{"min": 7000, "label": ">7000万", "color": "#FF6B6B"},
{"min": 5000, "max": 7000, "label": "5000-7000万", "color": "#FFA07A"},
{"min": 3000, "max": 5000, "label": "3000-5000万", "color": "#4ECDC4"},
{"min": 1000, "max": 3000, "label": "1000-3000万", "color": "#45B7D1"},
{"max": 1000, "label": "<1000万", "color": "#96CEB4"},
],
pos_left="left",
pos_bottom="10%",
),
tooltip_opts=opts.TooltipOpts(
formatter="{b}: {c}万元"
),
)
)
map_chart.render("echarts_map.html")
print("ECharts地图已生成!")
# ===== ECharts 组合图(折线+柱状) =====
months = ['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月']
sales = [1200, 1350, 1480, 1620, 1550, 1780, 1920, 2050, 1980, 2150, 2280, 2450]
growth_rate = [None, 12.5, 9.6, 9.5, -4.3, 14.8, 7.9, 6.8, -3.4, 8.6, 6.0, 7.5]
# 创建组合图
from pyecharts.charts import Bar, Line
bar_line = (
Bar(init_opts=opts.InitOpts(
theme=ThemeType.LIGHT,
width="1200px",
height="600px",
))
.add_xaxis(months)
.add_yaxis(
"销售额",
sales,
color="#4ECDC4",
yaxis_index=0,
itemstyle_opts=opts.ItemStyleOpts(border_radius=[5, 5, 0, 0]),
label_opts=opts.LabelOpts(is_show=False),
)
.extend_axis(
yaxis=opts.AxisOpts(
name="增长率(%)",
type_="value",
axislabel_opts=opts.LabelOpts(formatter="{value}%"),
splitline_opts=opts.SplitLineOpts(is_show=False),
)
)
.set_global_opts(
title_opts=opts.TitleOpts(title="月度销售额与增长率"),
tooltip_opts=opts.TooltipOpts(trigger="axis"),
yaxis_opts=opts.AxisOpts(name="销售额(万元)"),
)
)
# 添加折线(增长率)
line = (
Line()
.add_xaxis(months)
.add_yaxis(
"增长率",
growth_rate,
yaxis_index=1,
color="#FF6B6B",
linestyle_opts=opts.LineStyleOpts(width=3),
label_opts=opts.LabelOpts(
formatter=JsCode("function(x){return x.value[1] + '%'}")
),
)
)
# 组合
bar_line.overlap(line)
bar_line.render("echarts_bar_line.html")
print("ECharts组合图已生成!")
5. 数据适配引擎 | Data Adaptation Engine
data-visualization Skill 内置了智能数据适配引擎,能够自动推断数据类型、处理缺失值、进行数据聚合:
# 数据适配引擎核心实现
import pandas as pd
import numpy as np
from typing import Optional, Tuple, List
class DataAdapter:
"""
数据适配引擎——将原始数据转换为图表可用的格式
核心能力:类型推断、缺失值处理、数据聚合、格式转换
"""
def adapt(self, df: pd.DataFrame, request: 'VisualizationRequest') -> pd.DataFrame:
"""主入口:适配数据到图表所需格式"""
# 步骤1:推断列类型
df = self._infer_column_types(df)
# 步骤2:处理缺失值
df = self._handle_missing_values(df, request)
# 步骤3:数据聚合
if request.group_by:
df = self._aggregate_data(df, request)
# 步骤4:格式转换
df = self._convert_formats(df, request)
return df
def _infer_column_types(self, df: pd.DataFrame) -> pd.DataFrame:
"""推断列类型——自动识别日期、数值、分类列"""
for col in df.columns:
# 尝试转换为日期类型
if df[col].dtype == 'object':
try:
df[col] = pd.to_datetime(df[col])
print(f" 列 '{col}' 推断为日期类型")
continue
except (ValueError, TypeError):
pass
# 尝试转换为数值类型
try:
df[col] = pd.to_numeric(df[col].str.replace(',', ''))
print(f" 列 '{col}' 推断为数值类型")
continue
except (ValueError, TypeError):
pass
# 剩余为分类类型
nunique = df[col].nunique()
if nunique / len(df) < 0.5: # 唯一值占比<50%视为分类
df[col] = df[col].astype('category')
print(f" 列 '{col}' 推断为分类类型({nunique}个类别)")
return df
def _handle_missing_values(self, df: pd.DataFrame,
request: 'VisualizationRequest') -> pd.DataFrame:
"""处理缺失值——根据图表类型选择不同策略"""
missing_count = df.isnull().sum()
if missing_count.sum() == 0:
return df
print(f" 发现缺失值:{missing_count[missing_count > 0].to_dict()}")
for col in df.columns:
if df[col].isnull().sum() == 0:
continue
if pd.api.types.is_numeric_dtype(df[col]):
# 数值列:用中位数填充
df[col].fillna(df[col].median(), inplace=True)
print(f" 列 '{col}' 缺失值已用中位数填充")
elif pd.api.types.is_datetime64_any_dtype(df[col]):
# 日期列:用前值填充
df[col].fillna(method='ffill', inplace=True)
print(f" 列 '{col}' 缺失值已用前值填充")
else:
# 分类列:用众数填充
mode_val = df[col].mode()[0]
df[col].fillna(mode_val, inplace=True)
print(f" 列 '{col}' 缺失值已用众数填充")
return df
def _aggregate_data(self, df: pd.DataFrame,
request: 'VisualizationRequest') -> pd.DataFrame:
"""数据聚合——按指定维度聚合"""
group_cols = []
if request.x_column:
group_cols.append(request.x_column)
if request.group_by:
group_cols.append(request.group_by)
if not group_cols:
return df
# 确定聚合列和方式
agg_col = request.y_column
if agg_col is None:
# 自动选择第一个数值列
numeric_cols = df.select_dtypes(include=[np.number]).columns
if len(numeric_cols) > 0:
agg_col = numeric_cols[0]
if agg_col:
result = df.groupby(group_cols)[agg_col].agg(['sum', 'mean', 'count'])
print(f" 数据已按 {group_cols} 聚合,聚合列: {agg_col}")
return result.reset_index()
return df
def _convert_formats(self, df: pd.DataFrame,
request: 'VisualizationRequest') -> pd.DataFrame:
"""格式转换——确保数据格式符合图表要求"""
# 日期列格式化
for col in df.columns:
if pd.api.types.is_datetime64_any_dtype(df[col]):
# 根据图表类型选择日期粒度
if request.chart_type.value in ['line', 'area']:
df[col] = df[col].dt.strftime('%Y-%m-%d')
elif request.chart_type.value in ['bar']:
df[col] = df[col].dt.strftime('%Y-%m')
return df
# 使用示例
adapter = DataAdapter()
# 模拟原始数据
raw_data = pd.DataFrame({
'date': ['2026-01-15', '2026-01-20', '2026-02-10', '2026-02-18', '2026-03-05'],
'product': ['手机', '电脑', '手机', '平板', '电脑'],
'amount': ['4,500', '3,200', None, '1,800', '3,800'],
'quantity': [45, 32, 28, None, 38],
})
print("原始数据:")
print(raw_data)
print()
# 适配后的数据
adapted = adapter.adapt(raw_data, VisualizationRequest(
chart_type=ChartType.BAR,
data_source="",
x_column="product",
y_column="amount",
group_by="date",
))
print("\n适配后数据:")
print(adapted)
🛠️ 安装与使用 | Installation & Usage
安装方式1:pip 安装核心库
# 安装核心可视化库
pip install matplotlib seaborn plotly pyecharts
# 安装数据处理库
pip install pandas numpy
# 安装额外的可视化库(可选)
pip install altair bokeh folium # 声明式/浏览器端/地图
# 安装中文字体支持
pip install matplotlib-fontja # 日文字体(含部分中文支持)
# 验证安装
python -c "import matplotlib; print(f'matplotlib {matplotlib.__version__}')"
python -c "import plotly; print(f'plotly {plotly.__version__}')"
python -c "import pyecharts; print('pyecharts OK')"
安装方式2:conda 安装(推荐数据科学环境)
# 创建专用虚拟环境
conda create -n dataviz python=3.11 -y
conda activate dataviz
# 一次性安装所有库
conda install -c conda-forge matplotlib seaborn plotly pandas numpy -y
# pyecharts需要pip安装
pip install pyecharts
# 安装Jupyter支持(可选,用于交互式开发)
conda install -c conda-forge jupyterlab ipywidgets -y
pip install plotly-express
# 验证
python -c "import matplotlib, seaborn, plotly, pandas; print('All OK!')"
安装方式3:Docker 一键部署
# 拉取数据科学镜像(包含所有可视化库)
docker pull datasciencenotebook/datascience-notebook:latest
# 运行容器
docker run -it -p 8888:8888 \
-v $(pwd)/data:/home/jovyan/data \
-v $(pwd)/output:/home/jovyan/output \
datasciencenotebook/datascience-notebook
# 进入容器后安装额外库
pip install pyecharts folium
使用示例1:快速生成折线图 | Quick Line Chart
"""
示例1:快速生成销售趋势折线图
场景:展示2026年上半年的月度销售趋势
"""
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
# 设置中文字体——解决中文显示问题
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'PingFang SC']
matplotlib.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 准备数据
data = {
'月份': ['1月', '2月', '3月', '4月', '5月', '6月'],
'线上销售额': [280, 310, 345, 380, 365, 420],
'线下销售额': [180, 195, 210, 225, 215, 248],
}
df = pd.DataFrame(data)
# 创建折线图
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制两条折线
ax.plot(df['月份'], df['线上销售额'], marker='o', linewidth=2.5,
label='线上销售', color='#4ECDC4', markersize=8, zorder=3)
ax.plot(df['月份'], df['线下销售额'], marker='s', linewidth=2.5,
label='线下销售', color='#FF6B6B', markersize=8, zorder=3)
# 填充线下区域
ax.fill_between(df['月份'], df['线下销售额'], alpha=0.1, color='#FF6B6B')
# 添加数据标签
for i, (online, offline) in enumerate(zip(df['线上销售额'], df['线下销售额'])):
ax.annotate(f'{online}万', xy=(i, online), textcoords="offset points",
xytext=(0, 12), ha='center', fontsize=9, color='#4ECDC4')
ax.annotate(f'{offline}万', xy=(i, offline), textcoords="offset points",
xytext=(0, -18), ha='center', fontsize=9, color='#FF6B6B')
# 样式美化
ax.set_title('2026年上半年销售趋势', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('月份', fontsize=13)
ax.set_ylabel('销售额(万元)', fontsize=13)
ax.legend(fontsize=12, loc='upper left')
ax.grid(True, alpha=0.3, linestyle='--')
ax.set_ylim(100, 500)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.savefig('example1_line.png', dpi=150, bbox_inches='tight')
plt.show()
# 输出:
# 折线图已保存为 example1_line.png
# 线上销售6月环比增长15.1%
# 线下销售6月环比增长15.3%
使用示例2:生成带标注的柱状图 | Annotated Bar Chart
"""
示例2:生成产品销售对比柱状图
场景:对比各产品线的季度销售额
"""
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 准备数据
products = ['智能手机', '笔记本', '平板', '手表', '耳机']
q1 = [4500, 3200, 1800, 1200, 900]
q2 = [4800, 3500, 2100, 1500, 1100]
# 计算环比增长率
growth = [(q2[i] - q1[i]) / q1[i] * 100 for i in range(len(products))]
# 创建图表
fig, ax1 = plt.subplots(figsize=(12, 7))
# 柱状图
x = np.arange(len(products))
width = 0.35
bars1 = ax1.bar(x - width/2, q1, width, label='Q1',
color='#4ECDC4', edgecolor='white', zorder=3)
bars2 = ax1.bar(x + width/2, q2, width, label='Q2',
color='#FF6B6B', edgecolor='white', zorder=3)
# 在柱子顶部添加数值
for bar in bars1:
height = bar.get_height()
ax1.text(bar.get_x() + bar.get_width()/2., height + 50,
f'{int(height)}', ha='center', va='bottom',
fontsize=9, color='#4ECDC4', fontweight='bold')
for bar in bars2:
height = bar.get_height()
ax1.text(bar.get_x() + bar.get_width()/2., height + 50,
f'{int(height)}', ha='center', va='bottom',
fontsize=9, color='#FF6B6B', fontweight='bold')
# 第二个Y轴——环比增长率
ax2 = ax1.twinx()
line = ax2.plot(x, growth, 'o-', color='#FFA500', linewidth=2.5,
markersize=10, label='环比增长率', zorder=5)
# 标注增长率
for i, g in enumerate(growth):
ax2.annotate(f'{g:+.1f}%', xy=(i, g), textcoords="offset points",
xytext=(0, 15), ha='center', fontsize=10,
fontweight='bold', color='#FFA500')
# 样式
ax1.set_title('各产品线Q1 vs Q2销售额对比', fontsize=18, fontweight='bold', pad=20)
ax1.set_xlabel('产品线', fontsize=13)
ax1.set_ylabel('销售额(万元)', fontsize=13, color='#333333')
ax2.set_ylabel('环比增长率(%)', fontsize=13, color='#FFA500')
ax1.set_xticks(x)
ax1.set_xticklabels(products, fontsize=12)
ax1.grid(axis='y', alpha=0.2, linestyle='--', zorder=0)
ax1.spines['top'].set_visible(False)
# 合并图例
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=11)
plt.tight_layout()
plt.savefig('example2_bar.png', dpi=150, bbox_inches='tight')
plt.show()
# 输出:
# 柱状图已保存为 example2_bar.png
# 智能手机Q2环比增长+6.7%
# 笔记本Q2环比增长+9.4%
# 平板Q2环比增长+16.7%
使用示例3:生成热力图 | Heatmap Visualization
"""
示例3:生成特征相关性热力图
场景:分析数据集中各特征之间的相关性
"""
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 生成模拟数据——电商用户行为数据
np.random.seed(42)
n = 1000
df = pd.DataFrame({
'浏览时长': np.random.exponential(30, n),
'加购次数': np.random.poisson(3, n),
'搜索次数': np.random.poisson(5, n),
'收藏次数': np.random.poisson(2, n),
'下单金额': np.random.exponential(200, n),
'复购次数': np.random.poisson(1.5, n),
})
# 添加一些相关性
df['下单金额'] = df['浏览时长'] * 3 + df['加购次数'] * 50 + np.random.normal(0, 100, n)
df['复购次数'] = df['下单金额'] * 0.005 + df['收藏次数'] * 0.3 + np.random.normal(0, 1, n)
df['复购次数'] = df['复购次数'].clip(lower=0)
# 计算相关系数矩阵
corr = df.corr()
# 创建热力图
fig, ax = plt.subplots(figsize=(10, 8))
# 使用mask只显示下三角
mask = np.triu(np.ones_like(corr, dtype=bool))
# 绘制热力图
sns.heatmap(
corr,
mask=mask, # 只显示下三角
annot=True, # 显示数值
fmt='.2f', # 2位小数
cmap='RdBu_r', # 红蓝色系(红=正相关,蓝=负相关)
center=0, # 0为中心
vmin=-1, vmax=1, # 范围[-1, 1]
square=True, # 正方形单元格
linewidths=0.5, # 格子间距
linecolor='white',
cbar_kws={'label': '相关系数', 'shrink': 0.8},
ax=ax,
)
# 样式
ax.set_title('用户行为特征相关性矩阵', fontsize=18, fontweight='bold', pad=20)
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right', fontsize=11)
ax.set_yticklabels(ax.get_yticklabels(), rotation=0, fontsize=11)
plt.tight_layout()
plt.savefig('example3_heatmap.png', dpi=150, bbox_inches='tight')
plt.show()
# 输出:
# 热力图已保存为 example3_heatmap.png
# 浏览时长与下单金额相关系数: 0.72(强正相关)
# 加购次数与下单金额相关系数: 0.68(强正相关)
使用示例4:生成交互式Plotly图表 | Interactive Plotly Chart
"""
示例4:生成交互式销售仪表盘
场景:一个可缩放、悬停、筛选的销售数据看板
"""
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
# 准备数据——模拟6个月的销售数据
np.random.seed(42)
dates = pd.date_range('2026-01-01', '2026-06-30', freq='D')
df = pd.DataFrame({
'日期': np.tile(dates, 4),
'区域': np.repeat(['华东', '华南', '华北', '西南'], len(dates)),
'销售额': np.concatenate([
np.cumsum(np.random.randn(len(dates)) * 30 + 150), # 华东
np.cumsum(np.random.randn(len(dates)) * 25 + 120), # 华南
np.cumsum(np.random.randn(len(dates)) * 20 + 100), # 华北
np.cumsum(np.random.randn(len(dates)) * 15 + 80), # 西南
]),
})
# 创建交互式仪表盘
fig = make_subplots(
rows=2, cols=2,
subplot_titles=('累计销售趋势', '区域销售占比', '月度销售对比', '日销售分布'),
specs=[
[{"type": "scatter"}, {"type": "pie"}],
[{"type": "bar"}, {"type": "histogram"}],
],
vertical_spacing=0.12,
horizontal_spacing=0.08,
)
# 颜色方案
colors = {'华东': '#4ECDC4', '华南': '#FF6B6B', '华北': '#45B7D1', '西南': '#96CEB4'}
# 子图1:累计销售趋势
for region in ['华东', '华南', '华北', '西南']:
region_data = df[df['区域'] == region]
fig.add_trace(
go.Scatter(
x=region_data['日期'],
y=region_data['销售额'],
name=region,
line=dict(color=colors[region], width=2),
hovertemplate=f'<b>{region}</b><br>日期: %{{x|%Y-%m-%d}}<br>累计: %{{y:,.0f}}元<extra></extra>',
),
row=1, col=1,
)
# 子图2:区域销售占比
region_total = df.groupby('区域')['销售额'].sum()
fig.add_trace(
go.Pie(
labels=region_total.index,
values=region_total.values,
marker=dict(colors=[colors[r] for r in region_total.index]),
hole=0.4, # 环形图
textinfo='label+percent',
),
row=1, col=2,
)
# 子图3:月度销售对比
df['月份'] = df['日期'].dt.to_period('M').astype(str)
monthly = df.groupby(['月份', '区域'])['销售额'].sum().reset_index()
for region in ['华东', '华南', '华北', '西南']:
region_monthly = monthly[monthly['区域'] == region]
fig.add_trace(
go.Bar(
x=region_monthly['月份'],
y=region_monthly['销售额'],
name=region,
marker=dict(color=colors[region]),
showlegend=False,
),
row=2, col=1,
)
# 子图4:日销售分布
for region in ['华东', '华南', '华北', '西南']:
region_data = df[df['区域'] == region]
daily_sales = region_data.groupby('日期')['销售额'].first().diff().dropna()
fig.add_trace(
go.Histogram(
x=daily_sales,
name=region,
marker=dict(color=colors[region]),
opacity=0.6,
showlegend=False,
),
row=2, col=2,
)
# 全局布局
fig.update_layout(
title_text='销售数据交互式仪表盘',
title_font_size=22,
template='plotly_white',
height=900,
hovermode='x unified',
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
font=dict(family="Arial, SimHei, sans-serif"),
)
# 保存为HTML
fig.write_html("example4_dashboard.html")
print("交互式仪表盘已生成!请打开 example4_dashboard.html 查看")
print("功能:鼠标悬停查看数据、点击图例筛选区域、拖拽缩放时间范围")
使用示例5:生成ECharts地图 | ECharts Map
"""
示例5:生成全国销售分布地图
场景:展示各省份的销售额分布,支持悬停查看详情
"""
from pyecharts import options as opts
from pyecharts.charts import Map
from pyecharts.globals import ThemeType
# 各省份销售数据
province_data = [
("广东省", 8950), ("江苏省", 7820), ("浙江省", 6980),
("山东省", 5650), ("河南省", 4320), ("四川省", 4180),
("湖北省", 3850), ("湖南省", 3620), ("福建省", 3580),
("安徽省", 3150), ("河北省", 2980), ("北京市", 2850),
("上海市", 2720), ("辽宁省", 2480), ("陕西省", 2250),
("重庆市", 2080), ("江西省", 1850), ("云南省", 1720),
("广西壮族自治区", 1580), ("山西省", 1420),
("贵州省", 1280), ("吉林省", 1150), ("黑龙江省", 1080),
("内蒙古自治区", 980), ("新疆维吾尔自治区", 850),
("甘肃省", 720), ("海南省", 680), ("宁夏回族自治区", 450),
("青海省", 380), ("西藏自治区", 220), ("天津市", 1980),
]
# 创建地图
map_chart = (
Map(init_opts=opts.InitOpts(
theme=ThemeType.LIGHT,
width="1100px",
height="750px",
bg_color="#FAFAFA",
))
.add(
series_name="销售额",
data_pair=province_data,
maptype="china",
is_map_symbol_show=False,
label_opts=opts.LabelOpts(is_show=False),
itemstyle_opts=opts.ItemStyleOpts(
border_color="#FFFFFF",
border_width=1,
),
)
.set_global_opts(
title_opts=opts.TitleOpts(
title="全国各省份销售额分布",
subtitle="2026年Q1 | 单位:万元 | 悬停查看详情",
pos_left="center",
title_textstyle_opts=opts.TextStyleOpts(
font_size=22, font_weight="bold", color="#1A1A2E"
),
),
visualmap_opts=opts.VisualMapOpts(
min_=0,
max_=10000,
is_piecewise=True,
pieces=[
{"min": 7000, "label": "7000万以上", "color": "#C0392B"},
{"min": 5000, "max": 7000, "label": "5000-7000万", "color": "#E74C3C"},
{"min": 3000, "max": 5000, "label": "3000-5000万", "color": "#F39C12"},
{"min": 1000, "max": 3000, "label": "1000-3000万", "color": "#27AE60"},
{"min": 500, "max": 1000, "label": "500-1000万", "color": "#2ECC71"},
{"max": 500, "label": "500万以下", "color": "#82E0AA"},
],
pos_left="left",
pos_bottom="8%",
orient="vertical",
),
tooltip_opts=opts.TooltipOpts(
trigger="item",
formatter="{b}<br/>销售额:{c}万元",
),
)
)
map_chart.render("example5_map.html")
print("全国销售地图已生成!请打开 example5_map.html 查看")
print("功能:悬停查看省份详情、滚轮缩放、拖拽平移")
使用示例6:生成完整分析报告 | Full Analysis Report
"""
示例6:生成完整的数据分析报告(多图组合)
场景:一份包含6个图表的完整销售分析报告
"""
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns
import numpy as np
from matplotlib.gridspec import GridSpec
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
# 准备数据
np.random.seed(42)
n = 500
df = pd.DataFrame({
'日期': pd.date_range('2026-01-01', periods=n, freq='D'),
'产品': np.random.choice(['手机', '电脑', '平板', '手表'], n),
'区域': np.random.choice(['华东', '华南', '华北', '西南'], n),
'渠道': np.random.choice(['线上', '线下'], n, p=[0.65, 0.35]),
'销售额': np.random.exponential(500, n) + 100,
'数量': np.random.poisson(5, n),
})
# 创建6图组合报告
fig = plt.figure(figsize=(20, 16))
gs = GridSpec(3, 2, figure=fig, hspace=0.35, wspace=0.25)
# ===== 图1:月度销售趋势(折线图) =====
ax1 = fig.add_subplot(gs[0, 0])
df['月份'] = df['日期'].dt.to_period('M')
monthly = df.groupby('月份')['销售额'].sum()
monthly.index = monthly.index.astype(str)
ax1.plot(monthly.index, monthly.values, marker='o', color='#4ECDC4',
linewidth=2.5, markersize=8)
ax1.fill_between(range(len(monthly)), monthly.values, alpha=0.15, color='#4ECDC4')
ax1.set_title('月度销售趋势', fontsize=14, fontweight='bold')
ax1.set_ylabel('销售额(万元)')
ax1.grid(True, alpha=0.3, linestyle='--')
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
# ===== 图2:产品占比(饼图) =====
ax2 = fig.add_subplot(gs[0, 1])
product_sales = df.groupby('产品')['销售额'].sum()
colors_pie = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']
wedges, texts, autotexts = ax2.pie(
product_sales, labels=product_sales.index,
autopct='%1.1f%%', colors=colors_pie,
wedgeprops=dict(width=0.6, edgecolor='white', linewidth=2),
pctdistance=0.75,
)
for t in autotexts:
t.set_fontsize(10)
t.set_color('white')
t.set_fontweight('bold')
ax2.set_title('产品销售占比', fontsize=14, fontweight='bold')
# ===== 图3:区域对比(柱状图) =====
ax3 = fig.add_subplot(gs[1, 0])
region_sales = df.groupby('区域')['销售额'].sum().sort_values(ascending=True)
colors_bar = ['#96CEB4', '#45B7D1', '#4ECDC4', '#FF6B6B']
ax3.barh(region_sales.index, region_sales.values, color=colors_bar,
edgecolor='white', height=0.6)
for i, v in enumerate(region_sales.values):
ax3.text(v + 500, i, f'{v:,.0f}', va='center', fontsize=10, color='#333')
ax3.set_title('区域销售对比', fontsize=14, fontweight='bold')
ax3.set_xlabel('销售额(万元)')
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
# ===== 图4:渠道分布(堆叠柱状图) =====
ax4 = fig.add_subplot(gs[1, 1])
channel_product = df.pivot_table(values='销售额', index='产品',
columns='渠道', aggfunc='sum')
channel_product.plot(kind='bar', stacked=True, ax=ax4,
color=['#4ECDC4', '#FF6B6B'], edgecolor='white')
ax4.set_title('各产品渠道分布', fontsize=14, fontweight='bold')
ax4.set_ylabel('销售额(万元)')
ax4.set_xticklabels(ax4.get_xticklabels(), rotation=0)
ax4.legend(fontsize=10)
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
# ===== 图5:销售分布(箱线图) =====
ax5 = fig.add_subplot(gs[2, 0])
products_data = [df[df['产品'] == p]['销售额'] for p in ['手机', '电脑', '平板', '手表']]
bp = ax5.boxplot(products_data, labels=['手机', '电脑', '平板', '手表'],
patch_artist=True, widths=0.5,
medianprops=dict(color='#333333', linewidth=2))
for patch, color in zip(bp['boxes'], colors_pie):
patch.set_facecolor(color)
patch.set_alpha(0.7)
ax5.set_title('产品销售分布', fontsize=14, fontweight='bold')
ax5.set_ylabel('销售额(万元)')
ax5.grid(axis='y', alpha=0.3, linestyle='--')
ax5.spines['top'].set_visible(False)
ax5.spines['right'].set_visible(False)
# ===== 图6:相关性热力图 =====
ax6 = fig.add_subplot(gs[2, 1])
numeric_df = df[['销售额', '数量']].copy()
numeric_df['月份_num'] = df['日期'].dt.month
numeric_df['星期'] = df['日期'].dt.dayofweek
corr = numeric_df.corr()
sns.heatmap(corr, annot=True, fmt='.2f', cmap='RdBu_r',
center=0, square=True, linewidths=0.5,
cbar_kws={'shrink': 0.8}, ax=ax6)
ax6.set_title('特征相关性', fontsize=14, fontweight='bold')
# 全局标题
fig.suptitle('2026年销售数据分析报告', fontsize=24, fontweight='bold', y=0.98)
fig.text(0.5, 0.955, '数据来源:内部销售系统 | 生成时间:2026-05-15',
ha='center', fontsize=10, color='#888888')
plt.savefig('example6_report.png', dpi=150, bbox_inches='tight')
plt.show()
# 输出:
# 完整分析报告已保存为 example6_report.png
# 报告包含6个图表:趋势、占比、对比、分布、箱线、相关
🔧 高级配置 | Advanced Configuration
Agent 框架集成配置
# ===== LangChain 集成 =====
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 定义可视化工具
@tool
def create_line_chart(
data_csv: str,
x_column: str,
y_column: str,
title: str = "折线图",
output_path: str = "chart.png",
) -> str:
"""
创建折线图。
参数:
data_csv: CSV格式的数据字符串
x_column: X轴列名
y_column: Y轴列名
title: 图表标题
output_path: 输出文件路径
返回:图表文件路径
"""
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
from io import StringIO
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
df = pd.read_csv(StringIO(data_csv))
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(df[x_column], df[y_column], marker='o', linewidth=2.5, color='#4ECDC4')
ax.set_title(title, fontsize=18, fontweight='bold')
ax.grid(True, alpha=0.3, linestyle='--')
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight')
plt.close()
return f"图表已保存到 {output_path}"
@tool
def create_bar_chart(
data_csv: str,
x_column: str,
y_column: str,
title: str = "柱状图",
output_path: str = "chart.png",
) -> str:
"""
创建柱状图。
参数:
data_csv: CSV格式的数据字符串
x_column: X轴列名
y_column: Y轴列名
title: 图表标题
output_path: 输出文件路径
返回:图表文件路径
"""
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
from io import StringIO
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
df = pd.read_csv(StringIO(data_csv))
fig, ax = plt.subplots(figsize=(12, 6))
ax.bar(df[x_column], df[y_column], color='#4ECDC4', edgecolor='white')
ax.set_title(title, fontsize=18, fontweight='bold')
ax.grid(axis='y', alpha=0.3, linestyle='--')
plt.tight_layout()
plt.savefig(output_path, dpi=150, bbox_inches='tight')
plt.close()
return f"图表已保存到 {output_path}"
@tool
def create_interactive_chart(
data_csv: str,
chart_type: str,
x_column: str,
y_column: str,
title: str = "交互式图表",
output_path: str = "chart.html",
) -> str:
"""
创建交互式图表(Plotly)。
参数:
data_csv: CSV格式的数据字符串
chart_type: 图表类型(line/bar/scatter/pie/heatmap)
x_column: X轴列名
y_column: Y轴列名
title: 图表标题
output_path: 输出HTML文件路径
返回:HTML文件路径
"""
import pandas as pd
import plotly.express as px
from io import StringIO
df = pd.read_csv(StringIO(data_csv))
# 根据图表类型选择Plotly函数
chart_funcs = {
'line': px.line,
'bar': px.bar,
'scatter': px.scatter,
'pie': px.pie,
'heatmap': px.density_heatmap,
}
func = chart_funcs.get(chart_type, px.line)
if chart_type == 'pie':
fig = func(df, names=x_column, values=y_column, title=title)
else:
fig = func(df, x=x_column, y=y_column, title=title)
fig.update_layout(template='plotly_white')
fig.write_html(output_path)
return f"交互式图表已保存到 {output_path}"
# 创建Agent
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个数据可视化助手。你可以使用以下工具来创建各种图表:
- create_line_chart: 创建折线图
- create_bar_chart: 创建柱状图
- create_interactive_chart: 创建交互式图表
请根据用户的需求选择合适的图表类型,并生成图表。"""),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
tools = [create_line_chart, create_bar_chart, create_interactive_chart]
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 使用示例
result = agent_executor.invoke({
"input": "帮我用以下数据画一个柱状图,展示各城市销售额:\n城市,销售额\n北京,2850\n上海,2720\n广州,8950\n深圳,6200"
})
print(result['output'])
# ===== CrewAI 集成 =====
from crewai import Agent, Task, Crew
from crewai.tools import tool
# 定义可视化工具
@tool("create visualization")
def create_visualization(
chart_type: str,
data_description: str,
style: str = "default",
) -> str:
"""
创建数据可视化图表。
chart_type: 图表类型(line/bar/pie/scatter/heatmap)
data_description: 数据描述
style: 样式主题(default/dark/minimal/business)
"""
# 这里会调用实际的可视化逻辑
return f"已生成{chart_type}类型图表,样式:{style},数据:{data_description}"
# 创建可视化Agent
viz_agent = Agent(
role="数据可视化专家",
goal="根据数据和分析需求,生成最合适的可视化图表",
backstory="""你是一位资深的数据可视化专家,精通matplotlib、seaborn、
plotly和ECharts。你能够根据数据特征和分析目的,选择最合适的图表类型,
并生成美观、专业的可视化图表。你特别擅长:
- 根据数据特征推荐图表类型
- 设计美观的配色方案
- 处理中文显示问题
- 生成交互式图表""",
tools=[create_visualization],
verbose=True,
)
# 创建数据分析任务
viz_task = Task(
description="""分析以下销售数据并生成可视化图表:
1. 月度销售趋势折线图
2. 各产品销售占比饼图
3. 区域销售对比柱状图
数据:2026年Q1销售数据,包含日期、产品、区域、销售额等字段""",
expected_output="三个图表的文件路径和简要分析",
agent=viz_agent,
)
# 运行
crew = Crew(agents=[viz_agent], tasks=[viz_task])
result = crew.kickoff()
print(result)
自定义主题配置
# ===== 自定义主题完整配置 =====
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import cycler
# 主题1:科技感暗色主题
def apply_tech_dark_theme():
"""应用科技感暗色主题"""
matplotlib.rcParams.update({
# 背景
'figure.facecolor': '#0D1117',
'axes.facecolor': '#161B22',
'savefig.facecolor': '#0D1117',
# 文字
'text.color': '#C9D1D9',
'axes.labelcolor': '#8B949E',
'xtick.color': '#8B949E',
'ytick.color': '#8B949E',
# 边框
'axes.edgecolor': '#30363D',
'xtick.color': '#8B949E',
'ytick.color': '#8B949E',
# 网格
'grid.color': '#21262D',
'grid.alpha': 0.8,
# 线条
'lines.linewidth': 2.5,
'lines.color': '#58A6FF',
# 颜色循环
'axes.prop_cycle': cycler('color', [
'#58A6FF', # 蓝
'#3FB950', # 绿
'#D29922', # 黄
'#F85149', # 红
'#BC8CFF', # 紫
'#39D353', # 亮绿
'#79C0FF', # 浅蓝
'#FFA657', # 橙
]),
# 字体
'font.family': 'sans-serif',
'font.sans-serif': ['Arial Unicode MS', 'SimHei', 'PingFang SC'],
'axes.unicode_minus': False,
# 标题
'axes.titlesize': 18,
'axes.titleweight': 'bold',
'axes.labelsize': 13,
})
print("科技感暗色主题已应用!")
# 主题2:学术论文主题
def apply_academic_theme():
"""应用学术论文主题——符合期刊投稿要求"""
matplotlib.rcParams.update({
'figure.facecolor': 'white',
'axes.facecolor': 'white',
'font.family': 'serif',
'font.serif': ['Times New Roman', 'SimSun'],
'font.size': 12,
'axes.titlesize': 14,
'axes.labelsize': 12,
'xtick.labelsize': 10,
'ytick.labelsize': 10,
'legend.fontsize': 10,
'figure.dpi': 300, # 论文要求300dpi
'savefig.dpi': 300,
'savefig.bbox': 'tight',
'axes.linewidth': 0.8,
'lines.linewidth': 1.5,
'axes.prop_cycle': cycler('color', [
'#000000', # 黑
'#E69F00', # 橙
'#56B4E9', # 蓝
'#009E73', # 绿
'#F0E442', # 黄
'#0072B2', # 深蓝
'#D55E00', # 深橙
'#CC79A7', # 粉
]),
# 这些颜色经过色盲友好设计
})
print("学术论文主题已应用!")
# 主题3:商务报告主题
def apply_business_theme():
"""应用商务报告主题——适合PPT和商业报告"""
matplotlib.rcParams.update({
'figure.facecolor': '#FFFFFF',
'axes.facecolor': '#F8F9FA',
'font.family': 'sans-serif',
'font.sans-serif': ['Arial', 'SimHei', 'PingFang SC'],
'font.size': 12,
'axes.titlesize': 18,
'axes.titleweight': 'bold',
'axes.labelsize': 13,
'figure.figsize': (12, 7),
'figure.dpi': 150,
'axes.spines.top': False,
'axes.spines.right': False,
'grid.alpha': 0.2,
'grid.linestyle': '--',
'axes.prop_cycle': cycler('color', [
'#1B4F72', '#2E86C1', '#5DADE2', '#85C1E9',
'#AED6F1', '#D4E6F1', '#EAF2F8', '#F0F8FF',
]),
})
print("商务报告主题已应用!")
# 使用示例
apply_tech_dark_theme()
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot([1, 2, 3, 4, 5], [10, 25, 18, 32, 28], marker='o', markersize=10)
ax.set_title('科技感暗色主题示例')
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.grid(True)
plt.tight_layout()
plt.savefig('tech_dark_theme.png', dpi=150)
plt.close()
📊 效果对比 | Before & After
对比1:默认样式 vs 精美样式
| 维度 | 默认matplotlib | data-visualization Skill |
|---|---|---|
| 配色 | 默认蓝/红 | 精心设计的配色方案 ✔️ |
| 字体 | 英文默认 | 完美中文支持 ✔️ |
| 布局 | 经常重叠 | 智能避让 ✔️ |
| 标签 | 无/默认 | 自动添加数据标签 ✔️ |
| 图例 | 默认位置 | 智能定位 ✔️ |
| 网格 | 无 | 适度网格线 ✔️ |
| 边框 | 四边框 | 去掉上右边框 ✔️ |
| 导出 | 低分辨率 | 高DPI输出 ✔️ |
对比2:静态图表 vs 交互式图表
| 功能 | 静态(matplotlib) | 交互式(plotly/ECharts) |
|---|---|---|
| 缩放 | 不支持 | 鼠标滚轮缩放 ✔️ |
| 悬停 | 不支持 | 显示详细数据 ✔️ |
| 筛选 | 不支持 | 点击图例筛选 ✔️ |
| 联动 | 不支持 | 多图联动 ✔️ |
| 导出 | PNG/SVG/PDF | HTML/Widget ✔️ |
| 文件大小 | 小(KB级) | 较大(MB级) |
| 加载速度 | 快 | 需加载JS库 |
| 适用场景 | 论文/报告 | 仪表盘/大屏 ✔️ |
对比3:开发效率
| 任务 | 手动编码 | BI工具 | data-visualization |
|---|---|---|---|
| 简单折线图 | 15分钟 | 5分钟 | 30秒 ✔️ |
| 分组柱状图 | 30分钟 | 10分钟 | 1分钟 ✔️ |
| 热力图 | 45分钟 | 15分钟 | 1分钟 ✔️ |
| 交互式仪表盘 | 4小时 | 1小时 | 5分钟 ✔️ |
| 地图可视化 | 3小时 | 30分钟 | 2分钟 ✔️ |
| 完整分析报告 | 1天 | 4小时 | 15分钟 ✔️ |
🎨 定制项 | Customization Options
| 项目 | 修改方法 | 效果预览 |
|---|---|---|
| 图表类型 | chart_type: "line" | 折线图/柱状图/饼图/散点图等 |
| 渲染引擎 | engine: "plotly" | matplotlib/plotly/echarts |
| 主题风格 | theme: "dark" | default/dark/minimal/business/academic |
| 配色方案 | colors: ["#FF6B6B", ...] | 自定义颜色列表 |
| 图表尺寸 | width: 1200, height: 800 | 调整输出尺寸 |
| DPI | dpi: 300 | 150(屏幕)/300(论文)/600(印刷) |
| 输出格式 | format: "html" | png/svg/pdf/html |
| 中文字体 | font: "SimHei" | SimHei/Arial Unicode MS/PingFang SC |
| 交互模式 | interactive: true | 静态/交互式切换 |
| 数据标签 | show_labels: true | 显示/隐藏数据标签 |
| 网格线 | grid: true | 显示/隐藏网格线 |
| 图例位置 | legend: "upper left" | 控制图例位置 |
| 坐标轴范围 | ylim: [0, 100] | 自定义坐标轴范围 |
| 对数坐标 | log_scale: true | 线性/对数坐标切换 |
| 子图布局 | subplot: (2, 2) | 多子图网格布局 |
| 动画效果 | animation: true | ECharts/Plotly动画 |
🏗️ 架构对比 | Architecture Comparison
vs Tableau
| 维度 | Tableau | data-visualization Skill |
|---|---|---|
| 使用方式 | 拖拽操作 | 自然语言描述 ✔️ |
| 学习成本 | 中(需学操作) | 低(会说话就行) ✔️ |
| 自动化 | 有限 | 深度AI集成 ✔️ |
| 定制能力 | 受限于UI | 代码级定制 ✔️ |
| 价格 | 昂贵($70+/月) | 开源免费 ✔️ |
| 部署方式 | 桌面/云端 | 嵌入Agent ✔️ |
| 数据源 | 需连接器 | 直接读取文件 ✔️ |
| 图表类型 | 丰富 | 30+种 ✔️ |
| 交互能力 | 强 | 强 ✔️ |
| 协作 | 企业级 | Agent驱动 ✔️ |
| 类比 | 精装厨房 | AI厨师 🤖 |
vs ECharts手动开发
| 维度 | ECharts手动开发 | data-visualization Skill |
|---|---|---|
| 开发方式 | 手写配置项 | 自然语言描述 ✔️ |
| 代码量 | 100-500行/图 | 1句话/图 ✔️ |
| 学习成本 | 高(需学API) | 低(自然语言) ✔️ |
| 调试时间 | 长(配置复杂) | 短(AI自动调试) ✔️ |
| 样式定制 | 灵活但繁琐 | 智能推荐 ✔️ |
| 数据适配 | 手动转换 | 自动推断 ✔️ |
| 中文支持 | 需配置 | 开箱即用 ✔️ |
| 响应式 | 需手动实现 | 自动适配 ✔️ |
| 迭代速度 | 慢(改代码) | 快(改描述) ✔️ |
| 类比 | 手动挡汽车 | 自动驾驶 🚗 |
vs matplotlib手动编码
| 维度 | matplotlib手动 | data-visualization Skill |
|---|---|---|
| 开发效率 | 低(小时级) | 高(秒级) ✔️ |
| 代码量 | 50-200行/图 | 1句话/图 ✔️ |
| 样式美观度 | 取决于开发者 | AI优化 ✔️ |
| 中文字体 | 需手动配置 | 自动处理 ✔️ |
| 交互能力 | 无 | 可选交互式 ✔️ |
| 自动推荐 | 无 | 智能推荐图表类型 ✔️ |
| 错误处理 | 手动调试 | 自动调试 ✔️ |
| 类比 | 手工制作 | 智能工厂 🏭 |
🐛 常见问题 | Troubleshooting
1. 中文显示为方块?
# 问题:matplotlib图表中中文显示为方块或乱码
# 方案1:设置中文字体(推荐)
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'PingFang SC']
matplotlib.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 方案2:使用字体管理器指定字体路径
from matplotlib.font_manager import FontProperties
font_path = '/System/Library/Fonts/PingFang.ttc' # macOS
font = FontProperties(fname=font_path)
plt.title('中文标题', fontproperties=font)
# 方案3:清除字体缓存
import matplotlib
import shutil
cache_dir = matplotlib.get_cachedir()
shutil.rmtree(cache_dir) # 删除缓存目录
# 重启Python后字体会重新扫描
# 方案4:Linux系统安装中文字体
# sudo apt-get install fonts-wqy-microhei
# sudo fc-cache -fv
# 然后删除matplotlib缓存
2. 图表保存模糊?
# 问题:保存的图片分辨率低,看起来模糊
# 方案1:提高DPI
plt.savefig('chart.png', dpi=300) # 默认100,建议150-300
# 方案2:使用矢量格式
plt.savefig('chart.svg') # SVG矢量图,无限放大不失真
plt.savefig('chart.pdf') # PDF格式,适合论文
# 方案3:调整图片尺寸
fig, ax = plt.subplots(figsize=(16, 9)) # 更大的画布
# 方案4:Plotly导出高清图片
import plotly.io as pio
pio.write_image(fig, 'chart.png', scale=3) # 3倍缩放
3. 内存不足?
# 问题:处理大数据集时内存溢出
# 方案1:采样后绘图
df_sample = df.sample(n=10000, random_state=42)
# 方案2:分块处理
for chunk in pd.read_csv('large.csv', chunksize=50000):
# 增量更新图表
ax.plot(chunk['x'], chunk['y'], alpha=0.1)
# 方案3:使用更高效的数据类型
df['column'] = df['column'].astype('float32') # 从float64降级
# 方案4:关闭交互模式
plt.ioff() # 关闭交互模式,减少内存占用
fig, ax = plt.subplots()
# ... 绑图 ...
plt.savefig('chart.png')
plt.close(fig) # 显式关闭图形,释放内存
4. Plotly图表不显示?
# 问题:Plotly图表在Jupyter中不显示
# 方案1:安装Jupyter扩展
# pip install ipywidgets
# jupyter labextension install jupyterlab-plotly
# 方案2:使用离线模式
import plotly.offline as pyo
pyo.init_notebook_mode(connected=True)
# 方案3:直接输出HTML
fig.write_html("chart.html")
# 在浏览器中打开HTML文件
# 方案4:使用静态图片输出
fig.show(renderer="png") # 在Jupyter中显示静态图
fig.show(renderer="svg") # SVG格式
5. ECharts地图不显示?
# 问题:pyecharts地图显示空白
# 方案1:安装地图数据包
# pip install echarts-countries-pypkg # 全球国家地图
# pip install echarts-china-provinces-pypkg # 中国省级地图
# pip install echarts-china-cities-pypkg # 中国市级地图
# pip install echarts-china-counties-pypkg # 中国区县级地图
# 方案2:确认省份名称格式
# 必须使用完整名称,如"广东省"而非"广东"
province_data = [
("广东省", 8950), # 正确
# ("广东", 8950), # 错误!无法匹配
]
# 方案3:使用在线地图资源
from pyecharts.datasets import register_url
register_url("https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json")
6. 图例遮挡数据?
# 问题:图例位置不当,遮挡数据
# 方案1:调整图例位置
ax.legend(loc='upper left') # 左上角
ax.legend(loc='lower right') # 右下角
ax.legend(loc='outside upper right') # 图外右侧
# 方案2:图例放在图外
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
# 方案3:调整图例大小和透明度
ax.legend(fontsize=9, framealpha=0.8)
# 方案4:Plotly图例调整
fig.update_layout(
legend=dict(
orientation="h", # 水平排列
yanchor="bottom",
y=1.02,
xanchor="right",
x=1,
)
)
📚 扩展学习资源 | Extended Resources
官方文档
- Matplotlib 官方文档 - 最全面的matplotlib参考
- Seaborn 官方教程 - 统计可视化最佳实践
- Plotly Python 文档 - 交互式可视化完整指南
- PyECharts 文档 - ECharts Python封装文档
- Altair 文档 - 声明式可视化语法
可视化设计
- 数据可视化设计原则 - Fundamentals of Data Visualization
- 配色方案工具 - 在线配色方案生成器
- ColorBrewer - 地图配色专用工具
- Chart Suggestions - 根据数据选图表
实战教程
- Python 数据可视化实战
- Plotly 交互式图表教程
- ECharts 配置项手册
- 从数据到可视化 - 数据可视化决策树
Agent 框架集成
Conclusion | 结语
-
That's all for today~ - | 今天就写到这里啦~
-
Guys, ( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ See you tomorrow~~ | 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
-
Everyone, be happy every day! 大家要天天开心哦
-
Welcome everyone to point out any mistakes in the article~ | 欢迎大家指出文章需要改正之处~
-
Learning has no end; win-win cooperation | 学无止境,合作共赢
-
Welcome all the passers-by, boys and girls, to offer better suggestions! ~~~ | 欢迎路过的小哥哥小姐姐们提出更好的意见哇~~