数据分析的最后一公里是把结果画成图。但大部分人卡在这一步——数据有了,用什么图?参数怎么调?颜色怎么配?让大模型直接生成可视化代码,你说要什么图,它给你画出来。
思路不复杂:把数据和你的描述发给大模型,让它返回一段可直接运行的绘图代码。Python 这边执行代码就能出图。
先搭基础框架
```python
# chart_agent.py
import json
import subprocess
import sys
from pathlib import Path
from llm_client import LLMClient, Message
SYSTEM_PROMPT = """你是一个数据可视化专家。根据用户的需求和数据,生成 Python 代码绘制图表。
规则:
1. 只使用 matplotlib 和 seaborn
2. 代码必须完整可运行,包含 import
3. 图表保存到指定路径
4. 中文标签需要设置中文字体
5. 返回格式:```python 代码块
6. 图表要美观:配色合理、标签清晰、图例完整"""
class ChartAgent:
def __init__(self, client: LLMClient):
self.client = client
def generate_code(self, data_desc: str, chart_desc: str) -> str:
"""根据数据描述和图表需求生成代码"""
prompt = f"""数据说明:
{data_desc}
绘图需求:
{chart_desc}
请生成 Python 代码,保存图表到 output_chart.png。"""
messages = [
Message(role="system", content=SYSTEM_PROMPT),
Message(role="user", content=prompt),
]
resp = self.client.chat(messages, temperature=0.2)
return self._extract_code(resp.content)
def _extract_code(self, text: str) -> str:
"""从回复中提取 Python 代码"""
if "```python" in text:
start = text.index("```python") + 10
end = text.index("```", start)
return text[start:end].strip()
return text
def run_code(self, code: str) -> str:
"""执行生成的代码,返回运行结果"""
with open("_temp_chart.py", "w", encoding="utf-8") as f:
f.write(code)
result = subprocess.run(
[sys.executable, "_temp_chart.py"],
capture_output=True, text=True, timeout=30,
)
Path("_temp_chart.py").unlink(missing_ok=True)
if result.returncode != 0:
return f"运行失败: {result.stderr}"
if Path("output_chart.png").exists():
return "✅ 图表已生成: output_chart.png"
return "代码执行完成,但未生成图表文件"
if __name__ == "__main__":
client = LLMClient(
api_key="your-api-key",
base_url="https://api.deepseek.com",
model="deepseek-chat",
)
agent = ChartAgent(client)
data = """
月份: 1月,2月,3月,4月,5月,6月
销售额(万): 12, 18, 15, 22, 28, 35
利润(万): 3, 5, 4, 7, 9, 12
"""
chart = "画一个折线图,展示销售额和利润的趋势,要中文标签"
code = agent.generate_code(data, chart)
print(code)
print(agent.run_code(code))
```
第一次运行,让它画个销售趋势图。模型返回的代码大致长这样:
```python
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
matplotlib.rcParams['axes.unicode_minus'] = False
months = ['1月', '2月', '3月', '4月', '5月', '6月']
sales = [12, 18, 15, 22, 28, 35]
profit = [3, 5, 4, 7, 9, 12]
plt.figure(figsize=(10, 6))
plt.plot(months, sales, 'o-', label='销售额(万)', linewidth=2)
plt.plot(months, profit, 's--', label='利润(万)', linewidth=2)
plt.xlabel('月份')
plt.ylabel('金额(万)')
plt.title('上半年销售趋势')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('output_chart.png', dpi=150, bbox_inches='tight')
```
自动修正错误
生成的代码不一定一次跑通。字体找不到、列名不对、数据格式不匹配,常见问题加个自动修正。
```python
# chart_agent_v2.py
import traceback
class ChartAgentV2(ChartAgent):
def run_with_fix(self, data_desc: str, chart_desc: str,
max_retries: int = 3) -> tuple[str, str]:
"""生成代码并自动修正,返回 (最终代码, 运行结果)"""
code = self.generate_code(data_desc, chart_desc)
for attempt in range(max_retries):
# 写代码
with open("_temp_chart.py", "w", encoding="utf-8") as f:
f.write(code)
# 执行
result = subprocess.run(
[sys.executable, "_temp_chart.py"],
capture_output=True, text=True, timeout=30,
)
if result.returncode == 0 and Path("output_chart.png").exists():
Path("_temp_chart.py").unlink(missing_ok=True)
return code, "✅ 图表已生成"
# 出错了,让模型修复
error_msg = result.stderr or "图表文件未生成"
fix_prompt = f"""生成的代码运行出错:
{code}
错误信息:
{error_msg}
请修复代码,只返回修正后的完整 Python 代码。"""
messages = [
Message(role="system", content=SYSTEM_PROMPT),
Message(role="user", content=fix_prompt),
]
resp = self.client.chat(messages, temperature=0.1)
code = self._extract_code(resp.content)
print(f" 第 {attempt+1} 次修正...")
Path("_temp_chart.py").unlink(missing_ok=True)
return code, f"❌ 修正 {max_retries} 次仍未通过"
```
支持多种图表类型
不同的数据适合不同的图表。在 prompt 里说清楚数据特征,让模型自己选。
```python
chart_types = [
"画一个柱状图,对比各部门的业绩",
"画一个饼图,展示各产品线的占比",
"画一个散点图,分析广告投入和销售额的关系",
"画一个热力图,显示各月份各产品的销量",
"画一个箱线图,展示不同城市的薪资分布",
]
for desc in chart_types:
print(f"\n--- {desc} ---")
code, result = agent.run_with_fix(data_desc, desc)
print(result)
```
如果数据是 CSV 文件
大多数时候数据在 CSV 文件里,不是手写的。加一个 CSV 读取接口。
```python
# csv_chart.py
import pandas as pd
from chart_agent_v2 import ChartAgentV2
from llm_client import LLMClient
def csv_to_description(filepath: str, max_rows: int = 5) -> str:
"""读取 CSV 前几行,生成数据描述"""
df = pd.read_csv(filepath)
info = f"""文件: {filepath}
列名: {', '.join(df.columns)}
行数: {len(df)}
数据类型:
{df.dtypes.to_string()}
前 {max_rows} 行数据:
{df.head(max_rows).to_string(index=False)}
"""
return info
def main():
client = LLMClient(
api_key="your-api-key",
base_url="https://api.deepseek.com",
model="deepseek-chat",
)
agent = ChartAgentV2(client)
csv_path = input("CSV 文件路径: ").strip()
data_desc = csv_to_description(csv_path)
print(f"\n已加载: {csv_path}")
print(f"列: {data_desc.split(chr(10))[1]}")
chart_desc = input("想要什么图?(例如: 按月份画销售额折线图): ").strip()
print("\n生成中...")
code, result = agent.run_with_fix(data_desc, chart_desc)
print(f"\n{result}")
print(f"\n生成的代码:\n{code}")
if __name__ == "__main__":
main()
```
使用:
```bash
python csv_chart.py
CSV 文件路径: sales_data.csv
想要什么图?按月份画销售额折线图,不同产品用不同颜色
```
批量生成图表
数据分析报告经常要一次生成几十张图,一个个问太慢。写个批量模式。
```python
# batch_chart.py
import json
from pathlib import Path
from chart_agent_v2 import ChartAgentV2
from llm_client import LLMClient
def batch_generate(config_file: str):
"""从配置文件批量生成图表"""
with open(config_file, encoding="utf-8") as f:
tasks = json.load(f)
client = LLMClient(
api_key="your-api-key",
base_url="https://api.deepseek.com",
model="deepseek-chat",
)
agent = ChartAgentV2(client)
output_dir = Path("charts_output")
output_dir.mkdir(exist_ok=True)
for i, task in enumerate(tasks, 1):
print(f"\n[{i}/{len(tasks)}] {task['name']}")
data_desc = f"文件: {task['file']}\n需求: {task['description']}"
chart_desc = task['chart']
code, result = agent.run_with_fix(data_desc, chart_desc)
print(f" {result}")
# 保存图表和代码
chart_file = output_dir / f"{task['name']}.png"
if Path("output_chart.png").exists():
Path("output_chart.png").rename(chart_file)
code_file = output_dir / f"{task['name']}.py"
code_file.write_text(code, encoding="utf-8")
if __name__ == "__main__":
batch_generate("chart_tasks.json")
```
配置文件 `chart_tasks.json`:
```json
[
{
"name": "月度销售趋势",
"file": "sales_2026.csv",
"description": "2026年各月销售额和利润",
"chart": "双折线图展示销售趋势"
},
{
"name": "部门业绩对比",
"file": "dept_performance.csv",
"description": "各部门Q1和Q2业绩",
"chart": "分组柱状图对比Q1和Q2"
},
{
"name": "产品占比",
"file": "product_sales.csv",
"description": "各产品销售额占比",
"chart": "饼图展示占比,标出百分比"
}
]
```
几个实际踩过的坑
1. 中文字体问题。matplotlib 默认不支持中文,生成的代码必须显式设置中文字体。SimHei(黑体)在 Windows 上可用,macOS 用 Arial Unicode MS,Linux 要装文泉驿。系统不同,字体路径不同。比较稳的做法是用 font_manager 查找:
```python
import matplotlib.font_manager as fm
fonts = [f.name for f in fm.fontManager.ttflist if 'Hei' in f.name or 'CJK' in f.name]
```
2. 数据格式不一致。CSV 里的列名可能有空格、中文、特殊字符。生成的代码直接用列名当变量名可能报错。在数据描述里把列名用反引号括起来,让模型生成的代码也加上。
3. 图表太丑。默认的 matplotlib 配色和样式比较旧。可以在 system prompt 里要求使用 seaborn 样式。seaborn 的默认配色比 matplotlib 好看很多。
4. 生成的代码有安全隐患。大模型可能生成执行系统命令的代码(比如 os.system)。建议在沙箱里执行,或者至少审查生成的代码再运行。上面用了 subprocess 隔离,但也不是完全安全。
5. 模型对图表类型的理解有限。说"画个好看的图",不同模型理解不一样。最好明确说"折线图" "柱状图" "散点图",而不是"分析图" "趋势图"这类模糊描述。
6. 大数据集时间长。几千行的 CSV 传给模型,要消耗大量 token。建议只传前几行作为结构示意,完整数据让生成的代码自己去读 CSV 文件。上面 csv_chart.py 就是这么做的。
更多推荐




所有评论(0)