4. 数据分析与可视化:Matplotlib/Seaborn绘图、数据聚合与分组、统计指标计算
数据采集回来,原始数据就像一堆散落的拼图碎片。你得先看懂它长什么样,才能决定下一步怎么优化。我个人习惯,拿到数据第一件事不是跑模型,而是先画图、算指标、做分组聚合。说白了,就是先跟数据「混个脸熟」。
这一章,我们聚焦三个核心动作:绘图、分组聚合、统计指标计算。它们构成了数据分析的「三板斧」。你想想看,没有可视化,你只能盯着数字猜趋势;没有分组,你分不清不同维度的差异;没有统计指标,你没法量化「好」与「坏」。这三件事串起来,才是完整的数据洞察闭环。
核心逻辑:原始数据 → 分组聚合(提炼维度) → 统计指标(量化特征) → 可视化(呈现结论)。每一步都依赖上一步的输出,缺一环,分析就站不住脚。
4.1 数据聚合与分组:从整体到局部
分组聚合是数据分析的「切片机」。没有分组,你只能看到全局平均值,但全局平均往往掩盖了真实问题。举个例子,一个接口的平均响应时间是200ms,看起来还行。但按地域分组后,发现华东地区只有50ms,而西北地区高达800ms。你看,不分组,你根本不知道问题出在哪。
在Python里,我几乎只用pandas的groupby来做这件事。它灵活、高效,而且跟后续的可视化库无缝衔接。
4.1.1 groupby 的基本用法
核心就一句话:按什么分,对什么算,怎么算。
import pandas as pd
import numpy as np
# 模拟一份性能数据
df = pd.DataFrame({
'region': ['华东', '华北', '华东', '西北', '华北', '华东'],
'service': ['登录', '登录', '支付', '支付', '登录', '支付'],
'latency_ms': [45, 120, 55, 780, 95, 60],
'error_count': [1, 3, 0, 12, 2, 1]
})
# 按地域分组,计算延迟均值
region_group = df.groupby('region')['latency_ms'].mean()
print(region_group)
输出结果会让你立刻看到地域差异。我在项目中遇到过类似情况,按服务分组后发现「支付」服务的错误率是「登录」的5倍,这才定位到支付链路的超时配置有问题。
我的习惯:分组后一定用 .agg() 同时算多个指标,比如均值、中位数、标准差、计数。一次分组,多个维度,效率高得多。
# 一次分组,多个聚合指标
summary = df.groupby('region')['latency_ms'].agg(['mean', 'median', 'std', 'count'])
print(summary)
4.1.2 多级分组与透视表
有时候一个维度不够,需要按「地域+服务」两个维度同时分组。这时候groupby可以传一个列表。
# 多级分组
multi_group = df.groupby(['region', 'service'])['latency_ms'].mean()
print(multi_group)
但多级分组的输出是MultiIndex,看起来不够直观。我更喜欢用pivot_table,它能把结果转成表格形式,一目了然。
# 透视表:行=地域,列=服务,值=延迟均值
pivot = pd.pivot_table(df, values='latency_ms', index='region', columns='service', aggfunc='mean')
print(pivot)
| region | 登录 | 支付 |
|---|---|---|
| 华东 | 45.0 | 57.5 |
| 华北 | 107.5 | NaN |
| 西北 | NaN | 780.0 |
你看,西北地区只有支付服务的数据,而且延迟高得离谱。这就是分组聚合的价值——让异常无处藏身。
4.2 统计指标计算:用数字说话
分组之后,你得用统计指标来量化每组的表现。光看原始数据没用,你得算均值、中位数、标准差、百分位数。这些指标帮你回答三个问题:典型值是多少?波动大不大?有没有极端值?
4.2.1 描述性统计
pandas的describe()方法可以一口气输出多个常用指标。我每次拿到新数据,第一件事就是跑df.describe()。
# 全量数据的描述性统计
print(df['latency_ms'].describe())
输出会包含count、mean、std、min、25%、50%、75%、max。注意看中位数(50%)和均值的差距。如果两者相差很大,说明数据有偏斜,均值可能被极端值拉偏了。
我曾经踩过的坑:只看均值,忽略了中位数。有一次优化后均值从200ms降到了150ms,我以为效果很好。结果画了箱线图才发现,中位数根本没变,只是把几个极端慢的请求优化掉了,大部分用户感知不到变化。后来我养成了「均值+中位数+90分位」一起看的习惯。
4.2.2 分组后的统计指标
分组后算指标,才是真正的「精准打击」。
# 按地域分组,计算延迟的多个分位数
quantiles = df.groupby('region')['latency_ms'].quantile([0.5, 0.9, 0.99])
print(quantiles)
我个人习惯在性能分析中重点关注P50(中位数)、P90、P99。P50代表典型用户体验,P90代表大多数用户的上限,P99代表长尾问题。如果P99很高,说明有少量请求拖慢了整体体验,需要单独优化。
4.3 Matplotlib/Seaborn绘图:让数据说话
数字再精确,也不如图形直观。我见过太多人盯着表格看半天看不出问题,一画图立刻发现趋势。Matplotlib是底层绘图库,功能强大但API偏底层;Seaborn是基于Matplotlib的高级封装,默认样式好看,而且跟pandas配合得很好。
我的建议是:快速探索用Seaborn,精细定制用Matplotlib。
4.3.1 快速绘制分布图
先看数据分布。直方图+核密度估计(KDE)是最常用的组合。
import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文字体(避免乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 绘制延迟分布
sns.histplot(df['latency_ms'], kde=True, bins=10)
plt.title('延迟分布直方图')
plt.xlabel('延迟 (ms)')
plt.ylabel('频次')
plt.show()
这张图能告诉你:数据是正态分布还是偏态分布?有没有明显的双峰?我在项目中遇到过延迟分布呈现双峰的情况,后来发现是因为两个不同的数据中心混在了一起,分开分析后问题就清楚了。
4.3.2 分组对比:箱线图与柱状图
分组对比,箱线图是神器。它能同时展示中位数、四分位数、异常值。
# 按地域分组绘制箱线图
sns.boxplot(x='region', y='latency_ms', data=df)
plt.title('不同地域延迟分布对比')
plt.show()
箱线图里,箱子越扁说明数据越集中,箱子越长说明波动越大。圆圈点是异常值。如果某个组的箱子特别高,那就是优化重点。
柱状图适合展示分组后的均值或计数。
# 分组均值柱状图
region_mean = df.groupby('region')['latency_ms'].mean().reset_index()
sns.barplot(x='region', y='latency_ms', data=region_mean)
plt.title('各地域平均延迟对比')
plt.show()
4.3.3 相关性分析:热力图
当你有多个数值列时,热力图能快速展示它们之间的相关性。
# 计算相关系数矩阵
corr = df[['latency_ms', 'error_count']].corr()
# 绘制热力图
sns.heatmap(corr, annot=True, cmap='coolwarm', fmt='.2f')
plt.title('延迟与错误数的相关性')
plt.show()
相关系数接近1或-1,说明强相关;接近0,说明没什么关系。我在性能优化中经常用热力图来筛选特征,比如发现「延迟」和「错误数」强相关,那优化延迟很可能同时降低错误率。
一个小技巧:Seaborn的 pairplot() 可以一次性绘制所有数值列之间的散点图和分布图,适合快速探索多维数据。数据量大的时候慎用,会卡。
4.4 实战案例:从分组到可视化的一站式分析
把上面所有知识点串起来,做一个完整的分析流程。假设我们有一份API性能日志,包含地域、服务名、延迟、错误码。
# 模拟数据
np.random.seed(42)
data = {
'region': np.random.choice(['华东', '华北', '华南', '西北'], 200),
'service': np.random.choice(['登录', '支付', '查询', '下单'], 200),
'latency_ms': np.random.exponential(scale=100, size=200).astype(int),
'error': np.random.choice([0, 1], 200, p=[0.9, 0.1])
}
df = pd.DataFrame(data)
# 1. 分组聚合:按地域+服务分组,算延迟均值和错误率
summary = df.groupby(['region', 'service']).agg(
avg_latency=('latency_ms', 'mean'),
error_rate=('error', 'mean')
).reset_index()
# 2. 统计指标:计算P90延迟
p90 = df.groupby(['region', 'service'])['latency_ms'].quantile(0.9).reset_index()
p90.columns = ['region', 'service', 'p90_latency']
# 合并
summary = summary.merge(p90, on=['region', 'service'])
# 3. 可视化:绘制分组柱状图
plt.figure(figsize=(12, 6))
sns.barplot(x='region', y='avg_latency', hue='service', data=summary)
plt.title('各地域各服务平均延迟对比')
plt.ylabel('平均延迟 (ms)')
plt.legend(title='服务')
plt.show()
# 4. 可视化:错误率热力图
pivot_error = summary.pivot(index='region', columns='service', values='error_rate')
sns.heatmap(pivot_error, annot=True, cmap='Reds', fmt='.2%')
plt.title('各地域各服务错误率热力图')
plt.show()
这个流程走下来,你能立刻回答几个关键问题:哪个地域最慢?哪个服务错误率最高?P90延迟是否在可接受范围内?
核心原则:先分组聚合提炼维度,再算统计指标量化特征,最后可视化呈现结论。每一步都要问自己「这个结果能指导我做什么优化决策?」如果答案是否定的,说明分析方向偏了。
嗯,这一章的内容就到这里。分组聚合、统计指标、可视化,这三件事是数据分析的「铁三角」。你可以在自己的数据集上反复练习,直到形成肌肉记忆。记住,工具只是手段,洞察才是目的。