NumPy+Pandas+Matplotlib:AI 应用的数据分析三板斧
NumPy+Pandas+Matplotlib:AI 应用的数据分析三板斧
上一篇搞定了 MySQL 和 Redis 的数据存取,这篇把"存进去的数据"变成"看得见的洞察"——NumPy 做数值计算、Pandas 做数据清洗、Matplotlib 出图,三剑客配合才是 AI 工程师的日常分析流。
前言
上一篇我们用 pymysql 把数据写进 MySQL,用 Redis 给热数据加速。但数据存进去只是第一步——模型评测结果怎么对比?API 调用日志怎么统计?RAG 系统的 embedding 向量怎么批量处理?这些都需要在 Python 里直接分析数据,而不是导出到 Excel 手动操作。
Python 生态里做数据分析有三个绕不开的库,业内习惯叫"数据分析三剑客":NumPy 负责底层数值计算,Pandas 负责结构化数据处理,Matplotlib 负责可视化出图。这篇就把三者的核心用法串一遍,所有示例都围绕 AI 开发场景展开。
一、三剑客各司其职:谁干什么、怎么配合
1 NumPy:数值计算的发动机
NumPy(Numerical Python)是 Python 科学计算的基石。它提供了一个高性能多维数组对象 ndarray,底层用 C 语言实现,比纯 Python 列表快 10 到 100 倍。
在 AI 场景里,NumPy 的典型用途包括:
- 存储和处理 embedding 向量(一个 1536 维的 float 数组)
- 矩阵运算(attention 计算、相似度计算)
- 生成随机数据(模拟模型推理延迟、构造测试集)
NumPy 还有一个杀手特性叫广播(broadcasting)——不同形状的数组也能直接做运算,后面会展开讲。
2 Pandas:结构化数据的瑞士军刀
Pandas 建立在 NumPy 之上,提供了两个核心数据结构:
- Series:一维带标签数组,类似"有名字的列表"
- DataFrame:二维表格,类似 Excel 工作表或 SQL 表
AI 开发中 Pandas 的典型场景:
- 读取模型评测 CSV 结果,做筛选、排序、分组统计
- 清洗 API 调用日志(处理缺失字段、格式转换)
- 对 embedding 降维后的数据做探索性分析
一句话概括:NumPy 管"算",Pandas 管"整"——整理、清洗、聚合。
3 Matplotlib:让数据"开口说话"
Matplotlib 是 Python 最老牌的可视化库,API 风格和 MATLAB 类似。它能画折线图、散点图、柱状图、饼图等几乎所有常见图表。
AI 场景里的典型用途:
- 画出模型各版本的准确率变化曲线
- 对比不同供应商的推理延迟散点分布
- 展示 embedding 聚类后的二维投影
4 三剑客的协作流水线
三者不是孤立使用的,典型工作流是一个链式调用:
NumPy 生成/接收原始数据 → Pandas 清洗、筛选、聚合 → Matplotlib 出图
下面是一个完整串联示例,模拟 AI 模型评测场景:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg') # 指定图形后端
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
# ① NumPy:生成模拟评测数据(100 条模型推理记录)
np.random.seed(42)
n = 100
data = {
'model': np.random.choice(['GPT-4o', 'Claude-3', 'Gemini-Pro', 'Qwen-72B'], n),
'latency_ms': np.random.normal(800, 200, n).clip(200, 2000), # 推理延迟
'token_count': np.random.randint(50, 500, n), # 输出 token 数
'score': np.random.uniform(0.6, 1.0, n).round(3), # 质量评分
}
# ② Pandas:整理成 DataFrame 并做分组统计
df = pd.DataFrame(data)
summary = df.groupby('model').agg(
avg_latency=('latency_ms', 'mean'),
avg_score=('score', 'mean'),
total_tokens=('token_count', 'sum')
).round(2)
print(summary)
# ③ Matplotlib:画出各模型延迟分布
df.boxplot(column='latency_ms', by='model', figsize=(10, 5))
plt.title('各模型推理延迟分布')
plt.suptitle('') # 去掉 Pandas 自动加的标题
plt.ylabel('延迟 (ms)')
plt.show()
二、NumPy 实战:从数组创建到广播
1 ndarray 的创建与属性
ndarray 是 NumPy 的核心,所有运算都围绕它展开。创建方式非常灵活:
import numpy as np
# 从 Python 列表创建——比如存一个 embedding 向量
embedding = np.array([0.12, -0.34, 0.56, 0.78, -0.21])
print(embedding.shape) # (5,) —— 一维,5 个元素
# 创建全零矩阵——初始化 attention 权重
attention_mask = np.zeros((4, 4))
print(attention_mask)
# 创建全一阵——初始化偏置项
bias = np.ones((1, 768))
# 等间距序列——生成 token 位置编码
positions = np.arange(0, 512, 1) # 0 到 511
# 均匀分割——将概率区间等分
thresholds = np.linspace(0, 1, 11) # 0.0, 0.1, 0.2, ..., 1.0
# 随机数组——模拟正态分布的模型权重初始化
weights = np.random.randn(768, 768) # 标准正态分布
查看数组的关键属性:
arr = np.random.randn(4, 768) # 4 个 token 的 768 维 embedding
print(f'维度数: {arr.ndim}') # 2
print(f'形状: {arr.shape}') # (4, 768)
print(f'元素总数: {arr.size}') # 3072
print(f'数据类型: {arr.dtype}') # float64
2 索引、切片与形状变换
NumPy 的切片是视图而非副本,修改切片会影响原数组——这个特性在需要原地修改时非常高效,但也要小心意外副作用。
# 模拟 6 个 token 的 embedding 矩阵(每行一个 token 的向量)
embeddings = np.array([
[0.1, 0.2, 0.3, 0.4],
[0.5, 0.6, 0.7, 0.8],
[0.9, 1.0, 1.1, 1.2],
[1.3, 1.4, 1.5, 1.6],
[1.7, 1.8, 1.9, 2.0],
[2.1, 2.2, 2.3, 2.4],
])
# 取第一个 token 的向量
print(embeddings[0]) # [0.1 0.2 0.3 0.4]
# 取所有 token 的第 0 维特征(第一列)
print(embeddings[:, 0]) # [0.1 0.5 0.9 1.3 1.7 2.1]
# 取前 3 个 token 的前 2 维
print(embeddings[:3, :2])
# 布尔索引——筛选出第 0 维 > 1.0 的 token
print(embeddings[embeddings[:, 0] > 1.0])
# 形状变换——将 6×4 展平成一维
flat = embeddings.flatten()
print(flat.shape) # (24,)
# reshape——变成 3×8
reshaped = embeddings.reshape(3, 8)
print(reshaped.shape) # (3, 8)
3 数学运算与统计
# 模拟两个 token 序列的相似度矩阵计算
query = np.random.randn(1, 768) # 查询向量
keys = np.random.randn(10, 768) # 10 个候选 key
# 点积计算相似度(矩阵乘法)
scores = np.dot(query, keys.T) # (1, 768) @ (768, 10) = (1, 10)
print(f'相似度分数: {scores}')
# 统计运算——分析模型输出 token 数量分布
token_counts = np.array([45, 120, 89, 256, 67, 340, 98, 150, 200, 78])
print(f'平均 token 数: {np.mean(token_counts):.1f}')
print(f'标准差: {np.std(token_counts):.1f}')
print(f'最大: {np.max(token_counts)}, 最小: {np.min(token_counts)}')
print(f'总和: {np.sum(token_counts)}')
# 沿轴统计——分析多个模型的评测矩阵
eval_matrix = np.array([
[0.85, 0.78, 0.92], # 模型A 在三个测试集上的得分
[0.91, 0.88, 0.86], # 模型B
[0.76, 0.82, 0.79], # 模型C
])
print(f'每个模型的平均分: {np.mean(eval_matrix, axis=1)}') # 按行
print(f'每个测试集的平均分: {np.mean(eval_matrix, axis=0)}') # 按列
4 广播机制:不同形状也能算
广播是 NumPy 最优雅的特性之一。当两个数组形状不同时,NumPy 会自动扩展较小的数组,使它们形状兼容后再运算。规则是:从最后一维往前比,要么相等,要么其中之一为 1。
# 场景:给 embedding 矩阵的每一行加上不同的偏置
embeddings = np.random.randn(5, 768) # 5 个 token 的 768 维向量
position_bias = np.arange(5).reshape(5, 1) # 位置偏置,形状 (5, 1)
# 广播:(5, 768) + (5, 1) → (5, 768) + (5, 768)
biased = embeddings + position_bias
print(biased.shape) # (5, 768)
# 场景:所有 token 加上同一个 layer norm 的 gamma
gamma = np.array([1.2]) # 形状 (1,)
# 广播:(5, 768) * (1,) → (5, 768) * (1, 1) → (5, 768)
normalized = embeddings * gamma
三、Pandas 实战:DataFrame 才是核心
1 Series 与 DataFrame 的创建
import pandas as pd
import numpy as np
# Series:带标签的一维数组——比如存一个模型的各项指标
metrics = pd.Series(
[0.92, 0.87, 0.95, 0.78],
index=['accuracy', 'f1_score', 'recall', 'latency_s']
)
print(metrics['accuracy']) # 0.92
# DataFrame:从字典创建——AI 模型注册表
models_df = pd.DataFrame({
'model_name': ['GPT-4o', 'Claude-3.5', 'Gemini-1.5', 'Qwen-72B', 'DeepSeek-V3'],
'provider': ['OpenAI', 'Anthropic', 'Google', 'Alibaba', 'DeepSeek'],
'max_tokens': [128000, 200000, 1000000, 32768, 65536],
'cost_per_1k': [0.03, 0.015, 0.007, 0.004, 0.002], # 美元
'supports_vision': [True, True, True, False, False],
})
print(models_df)
print(f'形状: {models_df.shape}') # (5, 5)
print(f'列名: {list(models_df.columns)}')
print(models_df.dtypes) # 每列的数据类型
2 数据选择:loc 和 iloc 怎么区分?
这是 Pandas 新手最容易踩的坑。记住一句话:loc 按标签名,iloc 按位置号。
# 用 model_name 做行索引,方便按名称查找
models_df.set_index('model_name', inplace=True)
# loc:按标签索引——切片包含结束值
print(models_df.loc['GPT-4o']) # 单行
print(models_df.loc[['GPT-4o', 'Qwen-72B']]) # 多行
print(models_df.loc['GPT-4o':'Gemini-1.5']) # 切片,包含 Gemini-1.5
# iloc:按位置索引——切片不包含结束值(和 Python 列表一致)
print(models_df.iloc[0]) # 第一行
print(models_df.iloc[[0, 2, 4]]) # 第 1、3、5 行
print(models_df.iloc[0:3]) # 前 3 行(不含第 3)
# 选择列
print(models_df['provider']) # 单列返回 Series
print(models_df[['provider', 'cost_per_1k']]) # 多列返回 DataFrame
3 筛选过滤与排序
# 筛选:支持视觉的模型
vision_models = models_df[models_df['supports_vision'] == True]
print(vision_models)
# 多条件筛选:同时满足两个条件
cheap_high_cap = models_df[
(models_df['cost_per_1k'] < 0.01) &
(models_df['max_tokens'] > 50000)
]
print(cheap_high_cap)
# query 方法:用字符串写条件,更直观
result = models_df.query('cost_per_1k < 0.01 and max_tokens > 50000')
# 排序:按价格升序
print(models_df.sort_values('cost_per_1k'))
# 多列排序:先按供应商,再按价格降序
print(models_df.sort_values(['provider', 'cost_per_1k'], ascending=[True, False]))
4 缺失值处理与分组聚合
AI 开发中经常遇到数据不完整的情况——比如模型评测时某些指标未返回、API 日志里个别字段为空。
# 模拟一份不完整的评测记录
eval_data = pd.DataFrame({
'model': ['GPT-4o', 'Claude-3', 'GPT-4o', 'Gemini', 'Claude-3', 'GPT-4o'],
'benchmark': ['MMLU', 'MMLU', 'HumanEval', 'MMLU', 'HumanEval', 'GSM8K'],
'score': [0.87, np.nan, 0.92, 0.83, 0.78, np.nan],
})
# 检查缺失情况
print(eval_data.isnull().sum()) # 每列缺失数
# 删除含缺失值的行
clean = eval_data.dropna()
print(clean)
# 用均值填充缺失分数
eval_data['score'].fillna(eval_data['score'].mean(), inplace=True)
# 分组聚合:按模型统计平均分
model_avg = eval_data.groupby('model')['score'].agg(['mean', 'count'])
print(model_avg)
# 数据透视表:模型 × 基准测试 的得分矩阵
pivot = eval_data.pivot_table(
index='model', columns='benchmark', values='score', aggfunc='mean'
)
print(pivot)
四、Matplotlib 实战:两行代码出图
1 环境配置与中文显示
Matplotlib 新版需要指定后端,中文显示也需要额外配置,这些是固定套路:
import matplotlib
matplotlib.use('TkAgg') # 图形渲染后端
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体
plt.rcParams['axes.unicode_minus'] = False # 负号正常显示
2 折线图与散点图
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 折线图:追踪模型三个版本的准确率变化
versions = np.array([1, 2, 3, 4, 5])
accuracy = np.array([0.82, 0.85, 0.88, 0.87, 0.91])
plt.figure(figsize=(8, 5))
plt.plot(versions, accuracy, marker='o', color='#2E86AB', linewidth=2)
plt.title('模型版本迭代准确率变化')
plt.xlabel('版本号')
plt.ylabel('准确率')
plt.grid(alpha=0.3) # 半透明网格
plt.show()
# 散点图:推理延迟 vs 输出 token 数的关系
np.random.seed(42)
token_counts = np.random.randint(50, 500, 60)
latency = token_counts * 2.5 + np.random.randn(60) * 100 # 延迟 = token × 系数 + 噪声
plt.figure(figsize=(8, 5))
plt.scatter(token_counts, latency, alpha=0.6, color='#D8315B')
plt.title('推理延迟与输出 Token 数的关系')
plt.xlabel('输出 Token 数')
plt.ylabel('延迟 (ms)')
plt.grid(alpha=0.3)
plt.show()
常见问题
Q1:NumPy 的 reshape 和 resize 有什么区别?
reshape 返回新数组(或视图),要求元素总数不变;resize 可以改变元素总数,多余的截断、不够的重复填充,且原地修改。
Q2:loc 切片包含结束值,iloc 不包含——为什么设计成这样?
loc 按标签索引,语义上"从 A 到 B"包含两端更自然;iloc 按位置,和 Python 原生切片 [start:stop] 保持一致,不包含 stop。
Q3:Pandas 的 inplace=True 有什么坑?
inplace=True 是原地修改,不返回新对象。但 Pandas 官方已将其标记为可能在未来版本弃用,推荐用赋值写法:df = df.dropna() 而非 df.dropna(inplace=True)。
Q4:Matplotlib 画图不显示怎么办?
三个常见原因:① 没调 plt.show();② 后端没指定(新版需 matplotlib.use('TkAgg'));③ 在 Jupyter 里需要 %matplotlib inline。
Q5:三剑客的数据怎么互相转换?
NumPy → Pandas:pd.DataFrame(np_array);Pandas → NumPy:df.values 或 df.to_numpy();Pandas/NumPy → Matplotlib:直接传给 plt.plot() 等函数即可。
和 AI 大模型开发的关系
1. Embedding 批量处理:NumPy 向量化
import numpy as np
# 模拟 1000 条文本的 embedding(每条 1536 维)
embeddings = np.random.randn(1000, 1536)
# 向量化归一化——一行代码处理全量,比 for 循环快 50 倍
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
normalized = embeddings / norms
# 余弦相似度矩阵(1000 × 1000)
similarity = np.dot(normalized, normalized.T)
print(f'相似度矩阵形状: {similarity.shape}')
2. RAG 检索结果分析:Pandas 筛选与统计
import pandas as pd
# 模拟 RAG 检索日志:每条记录包含查询、召回文档、相关性评分
rag_logs = pd.DataFrame({
'query_id': ['q001', 'q001', 'q001', 'q002', 'q002', 'q003'],
'doc_id': ['d01', 'd05', 'd12', 'd03', 'd08', 'd01'],
'relevance': [0.92, 0.78, 0.45, 0.88, 0.33, 0.95],
'retrieved_rank': [1, 2, 3, 1, 2, 1],
})
# 只看 Top-2 召回结果
top2 = rag_logs[rag_logs['retrieved_rank'] <= 2]
print(f'Top-2 平均相关性: {top2["relevance"].mean():.3f}')
# 按 query 统计召回数量
query_stats = rag_logs.groupby('query_id').agg(
recall_count=('doc_id', 'count'),
avg_relevance=('relevance', 'mean')
)
print(query_stats)
3. 模型性能对比:Matplotlib 可视化
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('TkAgg')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 三个模型在六个基准测试上的得分
benchmarks = ['MMLU', 'HumanEval', 'GSM8K', 'BBH', 'HellaSwag', 'ARC']
gpt4 = [0.87, 0.92, 0.89, 0.85, 0.95, 0.91]
claude = [0.86, 0.90, 0.88, 0.84, 0.94, 0.90]
gemini = [0.85, 0.88, 0.86, 0.83, 0.93, 0.89]
x = range(len(benchmarks))
plt.figure(figsize=(10, 5))
plt.plot(x, gpt4, 'o-', label='GPT-4o', color='#10A37F')
plt.plot(x, claude, 's--', label='Claude-3.5', color='#D97757')
plt.plot(x, gemini, '^-.', label='Gemini-1.5', color='#4285F4')
plt.xticks(x, benchmarks)
plt.title('主流大模型基准测试对比')
plt.ylabel('得分')
plt.legend()
plt.grid(alpha=0.3)
plt.show()
4. API 调用成本分析:Pandas 透视表
import pandas as pd
import numpy as np
# 模拟一个月的 API 调用日志
np.random.seed(42)
logs = pd.DataFrame({
'date': pd.date_range('2025-07-01', periods=200, freq='H'),
'model': np.random.choice(['GPT-4o', 'Claude-3.5', 'Gemini-1.5'], 200),
'tokens': np.random.randint(100, 2000, 200),
'cost': np.random.uniform(0.001, 0.05, 200).round(4),
})
# 按天汇总成本
logs['day'] = logs['date'].dt.date
daily_cost = logs.pivot_table(
index='day', columns='model', values='cost', aggfunc='sum'
).fillna(0)
print(daily_cost.head())
print(f"总成本: ${logs['cost'].sum():.2f}")
小结
这篇把数据分析三剑客的核心用法串了一遍:NumPy 的 ndarray 创建、索引切片、广播机制;Pandas 的 DataFrame 选择(loc/iloc)、筛选、缺失值处理、分组聚合;Matplotlib 的折线图和散点图。三者配合的典型流程是「NumPy 生成数据 → Pandas 清洗分析 → Matplotlib 出图」。
#Python #NumPy #Pandas #Matplotlib #数据分析 #AI开发 #可视化
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)