第 10 篇:LLM 辅助数据分析——自动化 EDA、智能报告生成与自然语言查询

本文为「从零到落地:机器学习分析数据实战系列」第 10 篇,完整系列持续更新中。


前言

前 9 篇覆盖了从数据处理到模型部署的完整链路。从这篇开始进入高阶拓展板块——用大语言模型(LLM)让数据分析变得更高效、更智能、门槛更低。

回顾一下第 4 篇我们做 EDA(探索性数据分析)的流程:

  1. 手动调 df.describe() 看统计量
  2. 手动写 Matplotlib / Seaborn 画图
  3. 肉眼看图、读数字,总结出「扭矩和转速负相关」「故障样本只占 3.4%」等结论
  4. 把结论写成 Markdown 报告

整个过程花了大量时间在写代码画图 → 看图写文字上。而且写出来的报告质量完全取决于分析师的经验——新手可能漏掉关键发现,老手也可能因为疲劳而忽略细节。

如果有一个 AI 助手,能自动帮你完成这些工作呢?

这就是本篇的主题——用 LLM(大语言模型)辅助数据分析。我们会探索三个方向:

  1. 自动化 EDA——把数据丢给 LLM,让它自动生成完整的探索报告
  2. 自然语言查数据——运维人员不会写 Python?用中文问问题就能出分析结果
  3. 智能报告生成——模型预测结果 + LLM 自动生成可读的运维建议

本篇学完你将掌握

  • LLM 辅助数据分析的三种模式和各自适用场景
  • 用 OpenAI API(或本地开源模型)构建 EDA 助手的完整代码
  • Text-to-Pandas 的实现原理和安全性考量
  • 如何构建一个能调用 Python 工具的 EDA Agent
  • LLM 辅助 vs 手动 EDA 的效果对比

一、环境与工具准备

1.1 为什么现在才讲 LLM?

在前 9 篇里,我们故意没有引入 LLM。原因很简单:你必须先知道「正确答案」长什么样,才能判断 LLM 给的结果对不对

如果你没手动做过 EDA,就不知道 LLM 生成的报告漏了哪些关键发现;如果你没手动训练过模型,就无法判断 LLM 给的特征重要性是否合理。LLM 是加速器,不是替代品。

现在你已经有了完整的基础,是时候用 LLM 来提效了。

1.2 LLM 选型

本篇提供两种方案,按你的条件选:

方案 适用场景 优点 缺点
OpenAI API 有 API Key,网络可访问 效果最好,代码最简单 需要付费(约 ¥0.1-0.5/次分析)
本地开源模型(Ollama + Qwen) 内网环境、数据敏感场景 免费、数据不出内网 需要 GPU 显存 ≥ 8GB

1.3 环境安装

1
2
3
4
5
6
7
8
9
10
11
12
conda activate ml-data

# OpenAI API 客户端
pip install openai==1.40.*

# 本地模型方案(可选)
# 安装 Ollama: https://ollama.com/download
# ollama pull qwen2.5:14b

# LLM 辅助工具链
pip install langchain==0.2.* langchain-openai==0.1.*
pip install tabulate==0.9.* # 表格美化输出

1.4 API Key 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os

# 方式一:环境变量(推荐,不要把 Key 写在代码里)
# Windows PowerShell: $env:OPENAI_API_KEY="sk-xxx"
# Linux/macOS: export OPENAI_API_KEY="sk-xxx"

# 方式二:直接在代码里设置(仅用于测试)
# os.environ['OPENAI_API_KEY'] = 'sk-xxx'

# 验证 Key 是否有效
from openai import OpenAI
client = OpenAI()

try:
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': '你好,回复OK即可'}],
max_tokens=5
)
print(f'API 连接成功: {response.choices[0].message.content}')
except Exception as e:
print(f'API 连接失败: {e}')

⚠️ 安全提醒:API Key 绝不能提交到 Git 仓库。用 .env 文件或环境变量管理,.env 文件加入 .gitignore

1.5 加载数据

复用第 4-5 篇的 AI4I 2020 数据集和特征工程代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

def load_and_prepare(filepath='ai4i2020.csv'):
"""加载 AI4I 2020 数据集并完成特征工程(复用第 4-5 篇代码)。"""
df = pd.read_csv(filepath)

# 构造特征
df['Temperature_diff'] = df['Process temperature [K]'] - df['Air temperature [K]']
df['Power_approx'] = df['Rotational speed [rpm]'] * df['Torque [Nm]']
df['Speed_torque_ratio'] = df['Rotational speed [rpm]'] / (df['Torque [Nm]'] + 1e-8)

bins = [0, 50, 120, 180, 253]
labels = [0, 1, 2, 3]
df['Wear_stage_code'] = pd.cut(df['Tool wear [min]'], bins=bins, labels=labels).astype(float)

type_map = {'L': 0, 'M': 1, 'H': 2}
df['Type_code'] = df['Type'].map(type_map)

return df

df = load_and_prepare()
print(f'数据加载完成: {df.shape[0]} 行 × {df.shape[1]} 列')

二、业务理解:LLM 能帮数据分析师做什么?

在写代码之前,先搞清楚 LLM 在数据分析中到底能帮什么忙。

2.1 传统 EDA 的痛点

回忆一下第 4 篇做 EDA 的过程,至少有三个耗时环节:

环节 传统做法 耗时 痛点
统计计算 df.describe() + 手动整理 10 分钟 数字出来了,但每个数字意味着什么?需要经验解读
可视化 手写 Seaborn 代码画图 30 分钟 每种图都要写代码、调参数、选颜色
撰写报告 看图写 Markdown 结论 30 分钟 完全依赖分析师经验和表达能力

加起来,一份完整的 EDA 报告至少需要 1-2 小时。

2.2 LLM 辅助的三种模式

我们按「LLM 介入深度」从浅到深,分三个层次:

模式一:LLM 当「翻译官」

把统计结果和图表丢给 LLM,让它帮你写分析报告。你仍然负责写代码和画图,LLM 只负责解读和生成文字。

  • 适用场景:你已经会做 EDA,只是想加速报告撰写
  • 技术难度:最低,只需要调 API 发文本
  • 风险:最低,你仍然控制所有代码

模式二:LLM 当「分析师」

用自然语言问 LLM 问题(如「扭矩和转速的相关性如何?」),LLM 自动生成 Python 代码、执行代码、返回结果。

  • 适用场景:不会写 Python 的运维人员 / 想快速探索多个角度
  • 技术难度:中等,需要让 LLM 生成可执行的代码
  • 风险:中等,LLM 可能生成错误代码,需要沙箱环境

模式三:LLM 当「Agent」

给 LLM 一套工具(计算器、画图器、数据库查询器),让它自主规划分析步骤、调用工具、整合结果,完成端到端的分析任务。

  • 适用场景:构建完整的智能分析助手产品
  • 技术难度:最高,需要 Agent 框架
  • 风险:最高,LLM 可能走入死胡同,需要人工监督

本篇按这三个模式逐步深入。


三、模式一:LLM 当「翻译官」——自动化 EDA 报告

最简单的用法:你做完统计计算,把结果喂给 LLM,让它生成可读的分析报告。

3.1 核心思路

1
2
传统流程:数据 → Python 计算 → 人工看图 → 手写报告
LLM 辅助:数据 → Python 计算 → 结果文本 → LLM 生成报告

关键变化在最后一步:原来需要你自己写的「分析结论」,现在让 LLM 帮你写。你只需要把统计数字整理成结构化文本传给 LLM。

3.2 收集统计数据

先把所有 EDA 需要的统计量收集起来,打包成一段文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def collect_eda_stats(df):
"""收集 EDA 所需的全部统计信息,返回结构化文本。"""

lines = []
lines.append(f'## 数据集基本信息')
lines.append(f'- 行数: {df.shape[0]}')
lines.append(f'- 列数: {df.shape[1]}')
lines.append(f'- 列名: {list(df.columns)}')

# 数值特征统计
lines.append(f'\n## 数值特征统计描述')
desc = df.describe().round(2)
lines.append(desc.to_markdown())

# 缺失值
missing = df.isnull().sum()
missing_cols = missing[missing > 0]
if len(missing_cols) > 0:
lines.append(f'\n## 缺失值')
for col, cnt in missing_cols.items():
lines.append(f'- {col}: {cnt} ({cnt/len(df)*100:.1f}%)')
else:
lines.append(f'\n## 缺失值: 无')

# 类别特征分布
lines.append(f'\n## 类别特征分布')
for col in df.select_dtypes(include='object').columns:
vc = df[col].value_counts()
lines.append(f'\n### {col}')
for val, cnt in vc.items():
lines.append(f'- {val}: {cnt} ({cnt/len(df)*100:.1f}%)')

# 目标变量分布
if 'Machine failure' in df.columns:
lines.append(f'\n## 目标变量分布 (Machine failure)')
vc = df['Machine failure'].value_counts()
for val, cnt in vc.items():
lines.append(f'- {"故障" if val == 1 else "正常"}: {cnt} ({cnt/len(df)*100:.1f}%)')

# 相关性矩阵(只取数值特征)
lines.append(f'\n## 相关性矩阵(数值特征)')
num_cols = df.select_dtypes(include=[np.number]).columns.tolist()
# 去掉 ID 列
num_cols = [c for c in num_cols if c not in ['UDI', 'Product ID']]
corr = df[num_cols].corr().round(3)
lines.append(corr.to_markdown())

# 找出高相关性对(|r| > 0.3)
lines.append(f'\n## 高相关性特征对(|r| > 0.3)')
high_corr = []
for i in range(len(num_cols)):
for j in range(i+1, len(num_cols)):
r = corr.iloc[i, j]
if abs(r) > 0.3:
high_corr.append((num_cols[i], num_cols[j], r))
if high_corr:
for feat1, feat2, r in sorted(high_corr, key=lambda x: abs(x[2]), reverse=True):
lines.append(f'- {feat1}{feat2}: r = {r:.3f}')
else:
lines.append('- 无(所有特征对的 |r| ≤ 0.3)')

return '\n'.join(lines)

stats_text = collect_eda_stats(df)
print(f'统计信息收集完成,共 {len(stats_text)} 字符')
print(stats_text[:500]) # 预览前 500 字符

3.3 调用 LLM 生成报告

把统计文本发给 LLM,让它生成结构化的 EDA 报告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def generate_eda_report(stats_text, model='gpt-4o-mini'):
"""用 LLM 基于统计信息生成 EDA 分析报告。"""

system_prompt = """你是一位资深工业数据分析师。用户会给你一份数据集的统计信息,你需要生成一份完整的 EDA(探索性数据分析)报告。

报告要求:
1. 用中文撰写
2. 结构清晰,用 Markdown 格式
3. 每个发现都要解释「这意味着什么」和「对建模有什么影响」
4. 结合 CNC 铣床的业务背景解读数据(这是 CNC 铣床的预测性维护数据)
5. 如果发现潜在问题(如样本不均衡、异常值、高相关性),要明确指出并给出建议
6. 最后给出 3-5 条对后续建模的具体建议

不要编造数据中没有的信息。如果某个统计量缺失,直接说明。"""

user_prompt = f"""以下是 AI4I 2020 预测性维护数据集的统计信息,请生成完整的 EDA 报告:

{stats_text}"""

response = client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': user_prompt}
],
temperature=0.3, # 低温度,减少随机性,保证分析准确
max_tokens=3000
)

return response.choices[0].message.content

# 生成报告
report = generate_eda_report(stats_text)
print(report)

输出示例(LLM 生成,实际输出因模型版本而异):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# AI4I 2020 预测性维护数据集 EDA 报告

## 1. 数据概况
数据集包含 10,000 条记录、18 列,覆盖了 CNC 铣床运行的多个维度...

## 2. 关键发现

### 2.1 样本严重不均衡
故障样本仅占 3.4%,这意味着...建议采用 SMOTE 过采样或调整类别权重...

### 2.2 扭矩与转速的强负相关
Rotational speed 与 Torque 的相关系数为 -0.468,这符合电机特性...

## 3. 建模建议
1. 优先处理样本不均衡问题...
2. 构造温度差特征...
3. ...

💡 提示temperature=0.3 是关键设置。数据分析场景需要准确性而非创造性,所以温度要低。如果写营销文案,可以调高到 0.8-1.0。

3.4 加入对比视角

让 LLM 同时给出「传统分析师视角」和「LLM 可能遗漏的点」,做一个自我审视:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def review_eda_report(stats_text, report, model='gpt-4o-mini'):
"""让 LLM 审视自己生成的报告,找出可能遗漏的点。"""

prompt = f"""以下是一份数据集的统计信息和基于它生成的 EDA 报告。
请审视这份报告,找出:
1. 报告中遗漏了哪些重要发现?
2. 哪些结论可能过度推断(数据不足以支撑)?
3. 如果让你补充 3 个分析点,你会加什么?

## 统计信息
{stats_text}

## 报告
{report}"""

response = client.chat.completions.create(
model=model,
messages=[{'role': 'user', 'content': prompt}],
temperature=0.2,
max_tokens=1500
)

return response.choices[0].message.content

review = review_eda_report(stats_text, report)
print('=== 报告审视结果 ===')
print(review)

这一步的价值在于:LLM 审视 LLM 的输出,可以捕获第一轮生成时的盲点。当然,最终审核仍然需要人类——LLM 可能两轮都犯同一个错误。

3.5 保存报告

1
2
3
4
5
6
7
8
9
10
# 保存报告到文件
with open('eda_report_auto.md', 'w', encoding='utf-8') as f:
f.write('# LLM 自动生成的 EDA 报告\n\n')
f.write('> 基于 AI4I 2020 数据集,由 LLM 自动生成\n\n')
f.write(report)
f.write('\n\n---\n\n')
f.write('## 报告审视与补充\n\n')
f.write(review)

print('报告已保存到 eda_report_auto.md')

四、模式二:LLM 当「分析师」——自然语言查数据

模式一只是让 LLM 写报告,代码还是你写的。模式二更进一步:让 LLM 自己写代码来分析数据

4.1 为什么需要自然语言查数据?

在实际工厂里,不是所有人都会写 Python。运维主管可能想知道「高扭矩时段(> 60Nm)的故障率是多少」,但他不会写 df[df['Torque [Nm]'] > 60]['Machine failure'].mean()

如果能让他直接用中文问问题,系统自动生成代码、执行、返回结果——这就是 Text-to-Pandas(自然语言转 Pandas 代码)的价值。

4.2 Text-to-Pandas 的实现原理

整体流程很简单:

1
用户中文提问 → LLM 翻译成 Pandas 代码 → 执行代码 → 返回结果 → LLM 解读结果

关键环节是第二步:LLM 需要知道数据长什么样(有哪些列、每列什么类型),才能写出正确的代码。所以我们先把数据的「元信息」告诉 LLM。

4.3 构造数据元信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def build_dataframe_context(df):
"""构造 DataFrame 的元信息描述,让 LLM 了解数据结构。"""

lines = []
lines.append(f'DataFrame 名称: df')
lines.append(f'行数: {df.shape[0]}, 列数: {df.shape[1]}')
lines.append(f'\n列信息:')

for col in df.columns:
dtype = str(df[col].dtype)
nunique = df[col].nunique()
null_count = df[col].isnull().sum()

# 数值列:给范围
if df[col].dtype in [np.float64, np.int64, np.float32, np.int32]:
min_val = df[col].min()
max_val = df[col].max()
mean_val = df[col].mean()
lines.append(f'- {col}: {dtype}, 范围 [{min_val:.1f}, {max_val:.1f}], '
f'均值 {mean_val:.1f}, {nunique} 个唯一值, {null_count} 个缺失')
# 类别列:给取值
elif df[col].dtype == 'object':
values = df[col].unique().tolist()[:5]
lines.append(f'- {col}: {dtype}, 取值 {values}, {nunique} 个唯一值, {null_count} 个缺失')
else:
lines.append(f'- {col}: {dtype}, {nunique} 个唯一值, {null_count} 个缺失')

# 前 3 行示例
lines.append(f'\n数据前 3 行示例:')
lines.append(df.head(3).to_markdown(index=False))

# 业务背景
lines.append(f'\n业务背景: 这是 CNC 铣床的预测性维护数据集。')
lines.append(f'- Machine failure = 1 表示发生了故障')
lines.append(f'- TWF/HDF/PWF/OSF/RNF 是 5 种故障类型(1=发生,0=未发生)')
lines.append(f'- Type 是产品规格: L(低)/M(中)/H(高)')
lines.append(f'- Temperature_diff = 工艺温度 - 环境温度')
lines.append(f'- Power_approx = 转速 × 扭矩(近似功率)')
lines.append(f'- Wear_stage_code = 刀具磨损阶段(0-3)')

return '\n'.join(lines)

context = build_dataframe_context(df)
print(context)

4.4 Text-to-Pandas 核心函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def ask_data(question, df, context=None, model='gpt-4o-mini'):
"""用自然语言查询数据:中文提问 → LLM 生成代码 → 执行 → 返回结果。

参数:
question: 用户的中文问题
df: 要分析的 DataFrame
context: 数据元信息(如果为 None,会自动生成)
model: 使用的 LLM 模型
"""

if context is None:
context = build_dataframe_context(df)

# 第一步:让 LLM 生成 Pandas 代码
code_prompt = f"""你是一个数据分析助手。用户会用中文提问,你需要生成对应的 Pandas 代码来回答。

规则:
1. DataFrame 名称为 df,已经存在于环境中
2. 只生成代码,不要解释,不要 import
3. 把最终答案赋值给变量 `result`
4. result 应该是字符串、数字或小型 DataFrame
5. 如果需要分组统计,用 groupby
6. 所有输出用中文标注

数据结构:
{context}

用户问题:{question}"""

response = client.chat.completions.create(
model=model,
messages=[{'role': 'user', 'content': code_prompt}],
temperature=0, # 代码生成用 0 温度,确保确定性
max_tokens=500
)

code = response.choices[0].message.content.strip()

# 清理代码(去掉可能的 markdown 标记)
if code.startswith('```'):
code = code.split('\n', 1)[1] # 去掉第一行 ```python
if code.endswith('```'):
code = code[:-3]
code = code.strip()

print(f'📝 生成的代码:\n{code}\n')

# 第二步:执行代码
try:
exec_globals = {'df': df, 'np': np, 'pd': pd}
exec(code, exec_globals)
result = exec_globals.get('result', '代码执行完成,但未找到 result 变量')
print(f'✅ 执行结果:\n{result}')
return result, code
except Exception as e:
print(f'❌ 代码执行错误: {e}')
return None, code

4.5 实际测试

测试几个典型的运维问题:

1
2
# 问题 1:简单统计
result, code = ask_data('高扭矩(大于 60Nm)的样本有多少条?其中故障率是多少?', df, context)
1
2
# 问题 2:分组对比
result, code = ask_data('不同产品规格(Type L/M/H)的故障率分别是多少?', df, context)
1
2
# 问题 3:条件筛选
result, code = ask_data('刀具磨损超过 200 分钟且转速高于 1800rpm 的样本中,有多少发生了故障?', df, context)
1
2
# 问题 4:趋势分析
result, code = ask_data('按刀具磨损阶段分组,每个阶段的平均扭矩和故障率分别是多少?', df, context)

4.6 安全沙箱:为什么不能直接 exec?

上面的代码用了 exec() 直接执行 LLM 生成的代码——这在生产环境中是非常危险的

为什么?因为 LLM 可能生成恶意代码(如 import os; os.system('rm -rf /')),或者生成错误代码导致程序崩溃。在生产环境中,必须加沙箱保护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import subprocess
import tempfile

def safe_execute_code(code, df, timeout=10):
"""在子进程中安全执行 LLM 生成的代码。

用独立进程执行,即使代码有问题也不会影响主程序。
"""

# 把数据保存到临时文件
with tempfile.NamedTemporaryFile(suffix='.csv', delete=False, mode='w') as f:
df.to_csv(f.name, index=False)
csv_path = f.name

# 构造完整的执行脚本
script = f"""
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

df = pd.read_csv('{csv_path}')

{code}

print(result)
"""

# 在子进程中执行
try:
result = subprocess.run(
['python', '-c', script],
capture_output=True,
text=True,
timeout=timeout
)
if result.returncode == 0:
print(f'✅ 执行结果:\n{result.stdout}')
return result.stdout.strip()
else:
print(f'❌ 执行错误:\n{result.stderr}')
return None
except subprocess.TimeoutExpired:
print(f'⏰ 执行超时({timeout}秒),代码可能有死循环')
return None

⚠️ 核心原则:永远不要在 Web 服务中直接 exec() 用户输入或 LLM 生成的代码。要么用子进程沙箱,要么用 Docker 容器隔离。


五、模式三:LLM 当「Agent」——自主分析的智能助手

模式二里,LLM 只能回答单个问题。如果你想要它自主规划分析步骤,完成端到端的分析任务,就需要 Agent 模式。

5.1 什么是 Agent?

Agent(智能体)= LLM + 工具 + 规划能力。

  • 普通 LLM:你问一个问题,它回答一个答案
  • Agent:你给它一个任务,它自己拆解步骤、调用工具、根据中间结果决定下一步,直到任务完成

类比:普通 LLM 像一个只会回答问题的顾问,Agent 像一个会主动干活的实习生。

5.2 给 Agent 配备工具

Agent 需要「工具」才能和数据交互。我们给它三个基础工具:

工具 功能 什么时候用
run_code 执行 Pandas 代码 需要计算统计量、筛选数据、分组聚合
describe_dataframe 获取 DataFrame 的描述信息 需要了解数据结构
save_insight 保存分析发现 记录关键结论,最终整合成报告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 工具定义:每个工具是一个 Python 函数 + JSON Schema 描述

def run_code(code: str) -> str:
"""执行 Pandas 代码并返回结果。"""
try:
exec_globals = {'df': df.copy(), 'np': np, 'pd': pd}
exec(code, exec_globals)
result = exec_globals.get('result', '未找到 result 变量')
return str(result)
except Exception as e:
return f'执行错误: {str(e)}'

def describe_dataframe() -> str:
"""获取 DataFrame 的结构和统计信息。"""
return build_dataframe_context(df)

# 工具注册表(供 LLM 调用)
tools = {
'run_code': {
'function': run_code,
'description': '执行 Pandas 代码。代码中用 df 引用数据,结果赋值给 result 变量。',
'parameters': {
'code': {'type': 'string', 'description': '要执行的 Python/Pandas 代码'}
}
},
'describe_dataframe': {
'function': describe_dataframe,
'description': '获取 DataFrame 的结构信息(列名、类型、范围)。',
'parameters': {}
}
}

5.3 Agent 循环:思考 → 行动 → 观察

Agent 的核心是一个循环:

1
2
3
4
5
1. 思考(Thought):基于当前信息,下一步该做什么?
2. 行动(Action):调用一个工具
3. 观察(Observation):看工具返回了什么结果
4. 重复 1-3,直到收集到足够信息
5. 总结(Final Answer):整合所有发现,输出最终报告

这就是经典的 ReAct(Reasoning + Acting) 模式。

5.4 简易 Agent 实现

下面实现一个简化版的 EDA Agent(不依赖 LangChain,纯 OpenAI API):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import json

def eda_agent(task, df, max_steps=10, model='gpt-4o-mini'):
"""一个简易的 EDA Agent,能自主规划并执行数据分析任务。

参数:
task: 分析任务描述(中文)
df: 要分析的数据
max_steps: 最大步骤数(防止无限循环)
model: 使用的模型
"""

context = build_dataframe_context(df)
insights = [] # 收集分析发现

system_prompt = f"""你是一个数据分析 Agent。你的任务是用工具分析数据,回答用户的问题。

你可以使用以下工具:
1. run_code(code): 执行 Pandas 代码,结果赋值给 result 变量
2. describe_dataframe(): 获取数据结构信息

工作流程:
1. 先分析任务需要哪些步骤
2. 逐步调用工具获取信息
3. 每一步只调用一个工具
4. 当你收集到足够信息后,输出最终分析结果

规则:
- 用 JSON 格式回复,格式为 {{"thought": "你的思考", "action": "工具名", "action_input": "参数"}}
- 当你要输出最终结果时,格式为 {{"thought": "你的思考", "final_answer": "最终分析结果"}}
- run_code 的 action_input 是代码字符串
- describe_dataframe 不需要 action_input

数据结构:
{context}

业务背景:CNC 铣床预测性维护数据。Machine failure=1 是故障。Type L/M/H 是产品规格。"""

messages = [
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': f'分析任务:{task}'}
]

print(f'🤖 Agent 收到任务: {task}\n')

for step in range(max_steps):
print(f'--- 步骤 {step + 1}/{max_steps} ---')

# 让 LLM 决定下一步行动
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.2,
max_tokens=800
)

reply = response.choices[0].message.content.strip()
messages.append({'role': 'assistant', 'content': reply})

# 解析 LLM 的回复
try:
# 尝试提取 JSON
json_start = reply.find('{')
json_end = reply.rfind('}') + 1
action = json.loads(reply[json_start:json_end])
except json.JSONDecodeError:
print(f'⚠️ LLM 回复格式错误,尝试继续...')
messages.append({'role': 'user', 'content': '请用 JSON 格式回复'})
continue

thought = action.get('thought', '')
print(f'💭 思考: {thought}')

# 检查是否有最终答案
if 'final_answer' in action:
print(f'\n✅ 最终分析结果:\n{action["final_answer"]}')
return action['final_answer']

# 执行工具
tool_name = action.get('action', '')
tool_input = action.get('action_input', '')

if tool_name == 'run_code':
print(f'🔧 执行代码:\n{tool_input}')
result = run_code(tool_input)
print(f'📊 结果: {result[:500]}')

# 保存发现
insights.append({'step': step + 1, 'code': tool_input, 'result': result[:500]})

messages.append({'role': 'user', 'content': f'工具返回结果:\n{result}'})

elif tool_name == 'describe_dataframe':
result = describe_dataframe()
print(f'📋 数据结构已获取')
messages.append({'role': 'user', 'content': f'工具返回结果:\n{result}'})
else:
messages.append({'role': 'user', 'content': f'未知工具 {tool_name},请使用 run_code 或 describe_dataframe'})

print(f'\n⚠️ 达到最大步骤数 ({max_steps}),强制输出结果')
return '分析未完成,已达最大步骤数'

# 测试 Agent
task = """分析这份数据中的故障模式:
1. 哪些特征组合最容易导致故障?
2. 不同产品规格的故障模式有什么不同?
3. 给出 3 条降低故障率的具体建议"""

agent_result = eda_agent(task, df, max_steps=8)

5.5 Agent 的输出解读

Agent 的执行过程大致如下(实际输出因模型而异):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
🤖 Agent 收到任务: 分析这份数据中的故障模式...

--- 步骤 1/8 ---
💭 思考: 先了解数据的整体结构
🔧 describe_dataframe()
📋 数据结构已获取

--- 步骤 2/8 ---
💭 思考: 看看故障样本在各特征上的分布
🔧 run_code:
result = df.groupby('Machine failure')[['Air temperature [K]', ...]].mean().round(2)
📊 结果: 正常样本和故障样本的特征均值对比...

--- 步骤 3/8 ---
💭 思考: 进一步分析高扭矩+高转速的故障率
🔧 run_code:
high_torque = df[df['Torque [Nm]'] > 60]
result = f"高扭矩样本{len(high_torque)}条,故障率{high_torque['Machine failure'].mean():.1%}"
📊 结果: 高扭矩样本xxx条,故障率xx.x%...

... (继续分析)

--- 步骤 6/8 ---
✅ 最终分析结果:
1. 扭矩 > 55Nm 且刀具磨损 > 180min 时故障率最高(约 xx%)
2. Type H 产品故障率最高,主要故障类型是刀具磨损故障(TWF)
3. 建议:(1) 扭矩超过 55Nm 时降低转速 (2) 磨损超过 180min 强制换刀 (3) ...

💡 提示:Agent 的分析质量取决于底层 LLM 的能力。GPT-4o 级别效果最好,gpt-4o-mini 性价比高,本地开源模型(如 Qwen2.5-14B)也能用但需要更多引导。


六、模式二进阶:智能报告生成

前面讲了「用 LLM 分析数据」,现在讲「用 LLM 解读模型预测结果」——这在工厂里更有实际价值。

6.1 场景:模型预测后自动生成运维建议

第 5 篇的 XGBoost 模型输出了「这条数据预测为故障」,但运维人员需要的不只是是/否的答案,还需要:

  • 为什么模型认为会出故障?(哪些特征异常?)
  • 有多紧急?(预测概率是多少?)
  • 该怎么办?(具体的处置建议)

6.2 整合 SHAP 解释与 LLM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
import shap

# ---------- 训练模型(复用第 5 篇代码)----------
FEATURES = [
'Air temperature [K]', 'Rotational speed [rpm]', 'Torque [Nm]',
'Tool wear [min]', 'Temperature_diff', 'Power_approx',
'Speed_torque_ratio', 'Wear_stage_code'
]

X = df[FEATURES].values
y = df['Machine failure'].values

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)

model = XGBClassifier(
n_estimators=200, max_depth=5, learning_rate=0.1,
scale_pos_weight=28, eval_metric='logloss', random_state=42
)
model.fit(X_train, y_train)

# ---------- 用 SHAP 解释测试集 ----------
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

print(f'模型训练完成,SHAP 值计算完成')
print(f'测试集大小: {X_test.shape[0]} 条')

6.3 为单条预测生成智能报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def generate_prediction_report(sample_idx, features, values, shap_vals, 
prediction, proba, model=None):
"""为单条预测生成 LLM 辅助的运维建议报告。

参数:
sample_idx: 样本编号
features: 特征名列表
values: 特征值
shap_vals: SHAP 值(该样本的每个特征贡献)
prediction: 模型预测结果(0/1)
proba: 预测概率
"""

# 构造输入文本
lines = []
lines.append(f'## 样本 #{sample_idx} 预测结果')
lines.append(f'- 模型预测: {"⚠️ 故障" if prediction == 1 else "✅ 正常"}')
lines.append(f'- 故障概率: {proba:.1%}')

lines.append(f'\n## 特征值与 SHAP 贡献')
lines.append(f'| 特征 | 当前值 | SHAP 贡献 | 影响方向 |')
lines.append(f'| --- | --- | --- | --- |')

# 按 SHAP 绝对值排序
feature_shap = list(zip(features, values, shap_vals))
feature_shap.sort(key=lambda x: abs(x[2]), reverse=True)

for feat, val, shap_val in feature_shap:
direction = '推高故障概率 ↑' if shap_val > 0 else '降低故障概率 ↓'
lines.append(f'| {feat} | {val:.1f} | {shap_val:+.4f} | {direction} |')

input_text = '\n'.join(lines)

# 调用 LLM 生成运维建议
prompt = f"""你是一位 CNC 铣床运维专家。以下是故障预警模型对一台设备的诊断结果。

{input_text}

请给出:
1. **故障原因分析**:用通俗的语言解释为什么模型认为会(或不会)出故障
2. **紧急程度评估**:根据故障概率和特征异常程度,给出紧急等级(低/中/高/紧急)
3. **具体处置建议**:运维人员现在应该做什么(具体到操作步骤)
4. **预防措施**:长期来看,怎么避免类似情况

注意:
- 结合 CNC 铣床的实际工况
- 建议要具体可执行,不要泛泛而谈
- 如果故障概率低,也要说明需要持续监控的指标"""

response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
temperature=0.3,
max_tokens=1000
)

return response.choices[0].message.content

# ---------- 为测试集中的故障样本生成报告 ----------
# 找几个预测为故障的样本
y_pred = model.predict(X_test)
y_proba = model.predict_proba(X_test)[:, 1]

# 找 3 个预测为故障且概率最高的样本
fault_indices = np.where((y_pred == 1) & (y_proba > 0.5))[0]
top_fault = sorted(fault_indices, key=lambda i: y_proba[i], reverse=True)[:3]

for idx in top_fault:
print(f'\n{"="*60}')
report = generate_prediction_report(
sample_idx=idx,
features=FEATURES,
values=X_test[idx],
shap_vals=shap_values[idx],
prediction=y_pred[idx],
proba=y_proba[idx]
)
print(report)

输出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 样本 #1847 智能运维报告

### 故障原因分析
模型判断该设备有 78.3% 的概率即将发生故障,主要原因是:
1. **刀具磨损严重**(当前 231 分钟,已进入第 4 阶段)——这是最大的风险因素
2. **扭矩异常偏高**(67.2 Nm,远超均值 39.9)——说明切削阻力很大
3. **转速-扭矩比异常**——在高磨损下仍保持高转速,属于过载工况

### 紧急程度:🔴 高
建议在本批次加工完成后立即停机检查。

### 处置建议
1. 立即检查刀具磨损状态,如确认磨损超标则更换刀具
2. 检查工件材料硬度是否异常(可能混入了不合格批次的毛坯)
3. 适当降低转速至 1400rpm 以下,减少切削力

### 预防措施
- 建立刀具寿命管理台账,磨损超过 180 分钟强制更换
- 在加工程序中增加扭矩监控阈值,超过 55Nm 自动降速

七、效果对比:LLM 辅助 vs 手动 EDA

到目前为止,我们演示了 LLM 辅助数据分析的三种模式。现在做一个系统对比,帮你判断什么时候该用、什么时候不该用。

7.1 效率对比

维度 手动 EDA(第 4 篇) LLM 辅助 EDA(本篇) 差距
统计计算 30 分钟(写代码+调参) 10 分钟(写收集函数+调 API) 快 3 倍
报告撰写 30 分钟(看图写文字) 2 分钟(LLM 自动生成) 快 15 倍
自然语言查询 不适用(需要写 Python) 即时(10 秒/问题) 从无到有
运维建议 不适用(需要领域专家) 5 分钟(LLM 生成初稿) 从无到有

7.2 质量对比

维度 手动 EDA LLM 辅助 EDA
准确性 高(人类专家验证) 中(LLM 可能编造不存在的数据关系)
完整性 依赖分析师经验 较全面(LLM 会系统性检查各个维度)
可解释性 高(分析师知道为什么这样写) 中(LLM 是黑盒,可能给出奇怪的解释)
一致性 低(不同分析师写出来的报告差异大) 高(同一模型生成的报告风格统一)
业务深度 高(资深分析师理解工厂实际) 中(LLM 的业务知识来自训练数据,可能不够深入)

7.3 什么时候用 LLM、什么时候不用

场景 推荐 原因
快速初探一份新数据 ✅ 用 LLM 5 分钟内出报告,快速建立初步认知
给非技术人员解释数据 ✅ 用 LLM 自然语言查询降低门槛
生成运维建议初稿 ✅ 用 LLM LLM 给初稿,人类专家修改完善
发表学术论文 ❌ 不用 LLM 每个结论都需要严格验证,LLM 可能编造
关键生产决策 ❌ 不完全依赖 LLM LLM 建议仅供参考,必须人类专家终审
数据包含敏感信息 ⚠️ 看情况 用本地模型(Ollama),不要把数据发给云端 API

八、成本与性能考量

8.1 API 调用成本

操作 Token 消耗(估算) 成本(gpt-4o-mini) 成本(gpt-4o)
生成 EDA 报告 ~5000 input + ~3000 output ≈ ¥0.01 ≈ ¥0.15
单次自然语言查询 ~2000 input + ~200 output ≈ ¥0.003 ≈ ¥0.05
Agent 完整分析 ~15000 input + ~3000 output ≈ ¥0.03 ≈ ¥0.50
智能运维报告 ~2000 input + ~500 output ≈ ¥0.005 ≈ ¥0.08

💡 提示:gpt-4o-mini 性价比极高,日常分析用它就够了。gpt-4o 适合需要深度推理的复杂分析。如果你一天做 100 次查询,gpt-4o-mini 一天才花 ¥0.3。

8.2 延迟优化

LLM API 的延迟(响应时间)在数据分析场景中不可忽视:

操作 典型延迟 优化方法
EDA 报告生成 10-20 秒 分批统计,并行请求
自然语言查询 2-5 秒 缓存常见问题的回答
Agent 分析 30-60 秒 减少最大步骤数,使用更快的模型

对于交互式场景(如运维人员实时查询),2-5 秒的延迟完全可以接受。但对于自动化流水线,需要异步处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import asyncio
from openai import AsyncOpenAI

async_client = AsyncOpenAI()

async def async_ask_data(question, df, context):
"""异步版本的自然语言查询,适合同时处理多个请求。"""

code_prompt = f"""生成 Pandas 代码回答问题。DataFrame 名称: df。
只返回代码,不要解释。

{context}

问题:{question}"""

response = await async_client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': code_prompt}],
temperature=0,
max_tokens=500
)

code = response.choices[0].message.content.strip()
if code.startswith('```'):
code = code.split('\n', 1)[1]
if code.endswith('```'):
code = code[:-3]
code = code.strip()

exec_globals = {'df': df, 'np': np, 'pd': pd}
exec(code, exec_globals)
return exec_globals.get('result', '无结果')

# 批量查询示例
async def batch_query():
questions = [
'故障样本的平均扭矩是多少?',
'转速最高的 10 条记录中有多少是故障?',
'不同磨损阶段的样本数量分布',
]
context = build_dataframe_context(df)
tasks = [async_ask_data(q, df, context) for q in questions]
results = await asyncio.gather(*tasks)
for q, r in zip(questions, results):
print(f'Q: {q}\nA: {r}\n')

# asyncio.run(batch_query()) # 在 Jupyter 中运行

九、本地部署方案:数据不出内网

在工厂场景中,数据往往不能发到外部 API(安全合规要求)。这时需要用本地部署的开源模型。

9.1 Ollama + Qwen2.5

1
2
3
4
5
6
7
8
# 安装 Ollama(如果第 9 篇已经装过可以跳过)
# 下载地址: https://ollama.com/download

# 拉取 Qwen2.5 模型(14B 参数,需要 ~10GB 显存)
ollama pull qwen2.5:14b

# 如果显存不够,用 7B 版本(需要 ~5GB 显存)
ollama pull qwen2.5:7b

9.2 对接本地模型

Ollama 提供兼容 OpenAI 的 API,代码几乎不用改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 本地 Ollama 的 OpenAI 兼容接口
local_client = OpenAI(
base_url='http://localhost:11434/v1',
api_key='ollama' # Ollama 不需要真实 Key,但参数不能为空
)

def generate_eda_report_local(stats_text, model='qwen2.5:14b'):
"""用本地模型生成 EDA 报告(数据不出内网)。"""

response = local_client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': '你是资深工业数据分析师,用中文撰写分析报告。'},
{'role': 'user', 'content': f'请分析以下统计数据并生成 EDA 报告:\n{stats_text}'}
],
temperature=0.3,
max_tokens=3000
)

return response.choices[0].message.content

# 使用本地模型生成报告
# local_report = generate_eda_report_local(stats_text)
# print(local_report)

9.3 本地 vs 云端选型

维度 云端 API(OpenAI) 本地模型(Ollama + Qwen)
数据安全 数据发到外部服务器 数据完全不出内网
分析质量 更好(GPT-4o 是目前最强) 稍弱(开源模型有差距)
成本 按量付费 硬件一次性投入
延迟 受网络影响(2-10秒) 本地推理更快(1-3秒)
适合场景 研究/学习/非敏感数据 工厂/医疗/金融等敏感场景

📌 实际建议:先用云端 API 验证效果、跑通流程,确认方案可行后,再根据数据安全要求决定是否迁移到本地模型。


十、LLM 辅助 EDA 的最佳实践

经过前面几个模式的实战,总结几条落地经验:

10.1 Prompt 工程要点

要点 说明 示例
给角色 告诉 LLM 它是什么身份 「你是资深工业数据分析师」
给数据 提供结构化统计信息而非原始数据 df.describe() 的输出而非 CSV
给约束 明确输出格式和限制 「用 Markdown 格式,不超过 2000 字」
给业务背景 告诉 LLM 数据来自什么场景 「CNC 铣床预测性维护数据」
低温度 数据分析场景 temperature ≤ 0.3 减少随机性,提高准确性

10.2 常见坑与避坑方法

表现 解决方法
LLM 编造数据 报告中出现数据集中不存在的数字 在 Prompt 中明确「不要编造数据中没有的信息」
代码执行错误 LLM 生成的代码用了不存在的列名 提供完整的列名和类型信息
过度推断 从少量数据得出过于绝对的结论 要求 LLM 标注结论的置信度
上下文窗口溢出 数据太大,LLM 看不完 只发送统计摘要,不发原始数据
安全漏洞 LLM 生成恶意代码 用沙箱执行,限制可用模块

10.3 推荐的混合工作流

1
2
3
4
5
6
7
8
9
第一步:手动用 Pandas 做基础统计(5 分钟)

第二步:把统计结果发给 LLM,生成 EDA 报告初稿(2 分钟)

第三步:人类审核报告,修正错误结论(10 分钟)

第四步:针对感兴趣的发现,用自然语言追问 LLM(每个问题 10 秒)

第五步:LLM 生成最终报告 + 运维建议(5 分钟)

总耗时约 25 分钟,相比纯手动(1-2 小时)快 3-4 倍,且报告质量有保证。


总结与回顾

要点 总结
三种模式 翻译官(写报告)→ 分析师(查数据)→ Agent(自主分析),介入深度递增
自动化 EDA 收集统计量 → LLM 生成报告 → LLM 自我审视 → 人类终审
自然语言查询 中文提问 → LLM 生成 Pandas 代码 → 沙箱执行 → 返回结果
Agent 模式 LLM + 工具 + ReAct 循环,自主规划分析步骤
智能报告 模型预测 + SHAP 解释 + LLM 运维建议 = 端到端智能预警
安全 生产环境必须用沙箱执行代码,敏感数据用本地模型
成本 gpt-4o-mini 日常使用成本极低(¥0.3/天),性价比最优
最佳实践 低温度 + 结构化输入 + 业务背景 + 人类审核

核心认知

  1. LLM 是加速器,不是替代品——你必须先知道正确答案长什么样,才能判断 LLM 的输出对不对
  2. 人类审核不可省略——LLM 可能编造数据、过度推断、生成有漏洞的代码,每一步都需要人类把关
  3. 从简单开始——先用模式一(翻译官)提效,确认效果后再升级到模式二(分析师)和模式三(Agent)

下篇预告

第 11 篇:自然语言查数据 —— 本篇演示了基础的 Text-to-Pandas,下篇我们要构建一个完整的自然语言数据分析系统:支持多轮对话、自动纠错、图表生成、历史记录查询——让不会写代码的运维人员也能像和数据「聊天」一样分析设备运行状态。


本文为「从零到落地:机器学习分析数据实战系列」第 10 篇,完整系列持续更新中。