数学重学 - 20 描述统计与可视化

这是 数学重学路线图 阶段四的子页面

描述统计与可视化

用数字和图形概括数据的”长相”——不做推断,只做描述


为什么要学描述统计

你拿到一张有 100 万行的表,第一件事是什么?不是建模,而是先看看数据长什么样

描述统计就是你的”数据体检报告”

集中趋势告诉你”平均水平在哪”

离散程度告诉你”数据有多散”

分布形态告诉你”数据是对称的还是偏的”

可视化让你一眼看出文字和数字说不清的规律

做安全分析:流量数据的均值和方差能帮你划异常阈值

做大数据:EDA(探索性数据分析)是建模前的必经之路

做后端:性能指标的 P99/P95 就是分位数的直接应用


核心概念一:集中趋势

直觉比喻

一群人站在操场上,”集中趋势”回答的是——他们大概站在哪个区域?

均值(Mean)

所有值加起来除以个数

$$\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i$$

优点:用到了所有数据

缺点:极端值敏感——一个亿万富翁进村,全村”平均”变富翁

中位数(Median)

排好序后最中间那个值

奇数个取中间,偶数个取中间两个的平均

优点:不受极端值影响

缺点:没用到所有数据的信息

众数(Mode)

出现次数最多的值

可以有多个众数(双峰分布)

对分类数据特别有用(比如”最常见的攻击类型”)

三者的关系

对称分布:均值 ≈ 中位数 ≈ 众数

右偏分布:众数 < 中位数 < 均值

左偏分布:均值 < 中位数 < 众数

Python 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
from scipy import stats

data = [23, 25, 28, 30, 31, 31, 35, 42, 180] # 180是极端值

mean_val = np.mean(data) # 47.22 — 被180拉高了
median_val = np.median(data) # 31.0 — 不受极端值影响
mode_val = stats.mode(data, keepdims=True).mode[0] # 31 — 出现最多

print(f"均值: {mean_val:.2f}")
print(f"中位数: {median_val}")
print(f"众数: {mode_val}")
# 结论:当有极端值时,中位数比均值更能代表"典型水平"

核心概念二:离散程度

直觉比喻

两个班平均分都是 80,但 A 班所有人都在 78-82,B 班从 40 到 100 都有

离散程度就是告诉你数据有多”散”

极差(Range)

最大值 - 最小值

简单但粗糙,只看了两个端点

四分位距(IQR)

IQR = Q3 - Q1(第75百分位 - 第25百分位)

中间50%的数据落在这个范围内

箱线图的”箱子”就是 IQR

异常值判定:小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR

方差(Variance)

$$\sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2$$

样本方差除以 n-1(贝塞尔校正):$$s^2 = \frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2$$

单位是原数据单位的平方(不直观)

标准差(Standard Deviation)

$$\sigma = \sqrt{\sigma^2}$$

方差开根号,单位和原数据一致

正态分布下:68%数据在 μ±σ 内,95%在 μ±2σ 内

变异系数(CV)

$$CV = \frac{\sigma}{\bar{x}} \times 100%$$

消除量纲影响,用于比较不同尺度数据的离散程度

例:响应时间 CV=20% vs 吞吐量 CV=50%,说明吞吐量波动更大

Python 代码

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

data = np.array([23, 25, 28, 30, 31, 31, 35, 42, 55])

range_val = np.ptp(data) # 32(极差)
q1, q3 = np.percentile(data, [25, 75])
iqr = q3 - q1 # 四分位距
variance = np.var(data, ddof=1) # 样本方差(ddof=1用贝塞尔校正)
std_dev = np.std(data, ddof=1) # 样本标准差
cv = std_dev / np.mean(data) * 100 # 变异系数

print(f"极差: {range_val}")
print(f"Q1={q1}, Q3={q3}, IQR={iqr}")
print(f"方差: {variance:.2f}")
print(f"标准差: {std_dev:.2f}")
print(f"变异系数: {cv:.1f}%")

# 异常值检测
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
outliers = data[(data < lower) | (data > upper)]
print(f"异常值: {outliers}")

核心概念三:分布形态

偏度(Skewness)

衡量分布的不对称性

$$\text{Skewness} = \frac{1}{n}\sum\left(\frac{x_i - \bar{x}}{\sigma}\right)^3$$

正偏/右偏(skewness > 0):尾巴在右边

典型例子:收入分布——大部分人收入一般,少数人极高

负偏/左偏(skewness < 0):尾巴在左边

典型例子:考试成绩——大部分人考得不错,少数人很差

偏度 ≈ 0:近似对称

峰度(Kurtosis)

衡量分布的尖锐程度和尾巴厚度

正态分布的峰度=3(超额峰度=0)

高峰度(>3):尖峰+厚尾,极端值更多

安全场景:DDoS攻击流量往往是高峰度的

低峰度(<3):扁平+薄尾,极端值更少

Python 代码

1
2
3
4
5
6
7
8
9
10
11
from scipy import stats
import numpy as np

# 生成右偏数据(模拟收入分布)
income = np.random.lognormal(mean=10, sigma=1, size=10000)

skew = stats.skew(income)
kurt = stats.kurtosis(income) # 默认是超额峰度(减去了3)

print(f"偏度: {skew:.3f}") # 正数 → 右偏
print(f"峰度: {kurt:.3f}") # 正数 → 比正态更尖

核心概念四:可视化工具箱

直方图(Histogram)

看分布形态:数据集中在哪、是否对称、有无多峰

bin 的数量很重要:太少看不出细节,太多全是噪声

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示
plt.rcParams['axes.unicode_minus'] = False

data = np.random.normal(loc=100, scale=15, size=1000)

plt.figure(figsize=(10, 6))
plt.hist(data, bins=30, edgecolor='black', alpha=0.7, color='steelblue')
plt.axvline(np.mean(data), color='red', linestyle='--', label=f'均值={np.mean(data):.1f}')
plt.axvline(np.median(data), color='green', linestyle='--', label=f'中位数={np.median(data):.1f}')
plt.xlabel('值')
plt.ylabel('频数')
plt.title('正态分布直方图')
plt.legend()
plt.tight_layout()
plt.savefig('histogram.png', dpi=150)
plt.show()

箱线图(Box Plot)

看中位数 + 四分位 + 离群值

箱子 = IQR,中间线 = 中位数,须 = 1.5×IQR 范围,点 = 离群值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import matplotlib.pyplot as plt
import numpy as np

# 模拟多个服务的响应时间
service_a = np.random.exponential(scale=50, size=200)
service_b = np.random.normal(loc=80, scale=10, size=200)
service_c = np.concatenate([np.random.normal(60, 5, 180),
np.random.normal(200, 10, 20)]) # 有异常

plt.figure(figsize=(10, 6))
plt.boxplot([service_a, service_b, service_c],
labels=['服务A', '服务B', '服务C'],
patch_artist=True,
boxprops=dict(facecolor='lightblue'))
plt.ylabel('响应时间 (ms)')
plt.title('各服务响应时间箱线图')
plt.tight_layout()
plt.savefig('boxplot.png', dpi=150)
plt.show()

散点图(Scatter Plot)

看两变量之间的关系:正相关、负相关、无关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
import numpy as np

np.random.seed(42)
cpu_usage = np.random.uniform(10, 95, 100)
response_time = 20 + 2.5 * cpu_usage + np.random.normal(0, 15, 100)

plt.figure(figsize=(10, 6))
plt.scatter(cpu_usage, response_time, alpha=0.6, color='steelblue')
plt.xlabel('CPU 使用率 (%)')
plt.ylabel('响应时间 (ms)')
plt.title('CPU使用率 vs 响应时间')

# 加趋势线
z = np.polyfit(cpu_usage, response_time, 1)
p = np.poly1d(z)
plt.plot(sorted(cpu_usage), p(sorted(cpu_usage)), 'r--', linewidth=2,
label=f'趋势线: y={z[0]:.1f}x+{z[1]:.1f}')
plt.legend()
plt.tight_layout()
plt.savefig('scatter.png', dpi=150)
plt.show()

热力图(Heatmap)

看多变量之间的相关性矩阵

颜色深浅表示相关强度

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
import seaborn as sns
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 模拟服务器监控数据
np.random.seed(42)
n = 500
cpu = np.random.uniform(10, 95, n)
memory = 30 + 0.5 * cpu + np.random.normal(0, 8, n)
disk_io = 10 + 0.3 * cpu + np.random.normal(0, 5, n)
response_time = 20 + 1.5 * cpu + 0.8 * memory + np.random.normal(0, 10, n)
error_rate = np.clip(0.01 * cpu + np.random.normal(0, 1, n), 0, None)

df = pd.DataFrame({
'CPU使用率': cpu, '内存使用率': memory,
'磁盘IO': disk_io, '响应时间': response_time,
'错误率': error_rate
})

plt.figure(figsize=(10, 8))
corr = df.corr()
sns.heatmap(corr, annot=True, fmt='.2f', cmap='RdBu_r',
center=0, vmin=-1, vmax=1, square=True)
plt.title('服务器监控指标相关性热力图')
plt.tight_layout()
plt.savefig('heatmap.png', dpi=150)
plt.show()

核心概念五:相关系数

皮尔逊相关系数 r

$$r = \frac{\sum(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i - \bar{x})^2 \cdot \sum(y_i - \bar{y})^2}}$$

取值范围:**-1 到 +1**

r > 0:正相关(CPU升高 → 响应时间变长)

r < 0:负相关(缓存命中率升高 → 响应时间变短)

r ≈ 0:无线性相关(不代表无关!可能有非线性关系)

|r| 的解读:

0.0 - 0.3:弱相关

0.3 - 0.7:中等相关

0.7 - 1.0:强相关

相关 ≠ 因果

冰淇淋销量和溺水率正相关——不是冰淇淋导致溺水,是夏天(混淆变量)

安全领域:登录次数和攻击次数可能正相关,但不代表正常登录导致攻击

要建立因果关系,需要实验设计(下节 AB 测试会讲)

Python 代码

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
from scipy import stats

# CPU使用率 vs 响应时间
cpu = np.array([20, 35, 45, 55, 65, 75, 85, 90])
resp = np.array([50, 80, 95, 130, 160, 200, 250, 300])

r, p_value = stats.pearsonr(cpu, resp)
print(f"皮尔逊相关系数: r = {r:.4f}")
print(f"p值: {p_value:.6f}")
print(f"结论: {'强正相关' if r > 0.7 else '中等相关' if r > 0.3 else '弱相关'}")

综合实战: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
import pandas as pd
import numpy as np

# 模拟一天的API请求日志
np.random.seed(42)
n = 10000
df = pd.DataFrame({
'response_time_ms': np.random.lognormal(mean=4, sigma=0.8, size=n),
'status_code': np.random.choice([200, 200, 200, 200, 301, 400, 404, 500], size=n),
'request_size_kb': np.random.exponential(scale=5, size=n),
'cpu_percent': np.random.beta(2, 5, size=n) * 100,
})

# 一键描述统计
print("=" * 60)
print("基础描述统计")
print("=" * 60)
print(df.describe())

print("\n分位数详情:")
for col in ['response_time_ms', 'cpu_percent']:
percentiles = df[col].quantile([0.5, 0.9, 0.95, 0.99])
print(f"\n{col}:")
for p, v in percentiles.items():
print(f" P{int(p*100)}: {v:.2f}")

print("\n偏度和峰度:")
for col in df.select_dtypes(include=[np.number]).columns:
print(f" {col}: 偏度={df[col].skew():.3f}, 峰度={df[col].kurtosis():.3f}")

应用场景

大数据应用

EDA(探索性数据分析):拿到新数据集第一步就是 describe() + 画图

特征分析:相关系数矩阵帮你筛选和目标变量关系强的特征

数据质量报告:缺失值比例、异常值数量、分布偏斜情况

ETL 数据校验:上游数据的均值/方差突然变化 → 数据源可能出问题了

安全应用

流量基线建模:正常流量的均值和标准差 → 超过 μ+3σ 可能是异常

异常模式识别:请求大小的分布突然从单峰变双峰 → 可能混入了恶意流量

攻击特征分析:不同攻击类型的请求特征(大小、频率、时间间隔)统计对比

蜜罐数据分析:对蜜罐捕获的攻击数据做描述统计,画出攻击时间热力图

后端应用

性能数据分析:P50/P90/P95/P99 分位数比均值更有意义

容量规划可视化:CPU/内存/磁盘的趋势图 + 箱线图

SLO 监控:基于分位数定义 SLO(如 P99 响应时间 < 500ms)

性能回归检测:新版本上线前后的响应时间分布对比


常见误区

误区1:只看均值不看分布

均值 100ms 的服务可能 P99 是 5000ms——用户体验天差地别

永远要看分位数和分布图

误区2:把相关当因果

相关系数高只说明两个变量”一起变化”,不说明谁导致谁

误区3:忽略样本量

3 个数据点算出 r=0.99 没有统计意义

描述统计也需要足够的数据量

误区4:对非正态数据使用均值±标准差

严重偏态的数据(如响应时间),用中位数±IQR更合理

误区5:直方图 bin 数选择随意

bin 太少丢失细节,bin 太多全是噪声

经验法则:Sturges 公式 bins = 1 + 3.322 × log(n)


练习题

题目1:基础描述统计

给定数据 [12, 15, 18, 22, 25, 28, 30, 35, 42, 200]

(a) 分别计算均值、中位数、众数

(b) 哪个集中趋势指标最能代表这组数据的”典型水平”?为什么?

(c) 计算标准差和 IQR,用 IQR 方法找出异常值

题目2:分布形态判断

某网站每日访问量的偏度为 2.3,峰度(超额)为 8.1

(a) 这个分布是左偏还是右偏?

(b) 和正态分布相比,极端值多还是少?

(c) 这种分布下,均值和中位数哪个更大?

题目3:相关性分析实战

你有一个后端服务的监控数据,包含 CPU 使用率、内存使用率、并发连接数、响应时间

(a) 用 Python 生成模拟数据,计算相关系数矩阵

(b) 画出热力图,找出和响应时间相关性最强的指标

(c) 相关系数 r=0.85 能否说明”CPU 使用率升高导致响应时间变长”?为什么?

题目4:安全场景

蜜罐一周收到的攻击请求大小(KB):[0.5, 0.8, 1.2, 1.5, 2.0, 2.3, 2.5, 3.0, 45.0, 120.0]

(a) 画出箱线图,标记离群值

(b) 最后两个值(45.0, 120.0)可能是什么类型的攻击?

(c) 如果用均值作为”正常请求大小”的参考,合理吗?应该用什么?

题目5:综合可视化

用 Python 生成一个包含 1000 条记录的模拟日志数据集(响应时间、状态码、请求大小)

要求画出:(a) 响应时间直方图 (b) 各状态码占比饼图 (c) 请求大小 vs 响应时间散点图

观察并写出你的发现


小结

描述统计是数据分析的第一步,也是最重要的一步

集中趋势(均值/中位数/众数)告诉你”中心在哪”

离散程度(方差/标准差/IQR)告诉你”有多散”

分布形态(偏度/峰度)告诉你”形状如何”

可视化让数字变成直觉

相关系数量化两个变量的线性关系——但相关不等于因果

下一节 21-概率分布 我们将学习数据背后的概率模型


上一章 目录 下一章
19-哈希数学 数学重学路线图 21-概率分布