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 的 reshaperesize 有什么区别?
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.valuesdf.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开发 #可视化

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐