这是 数学重学路线图 阶段四的子页面
描述统计与可视化 用数字和图形概括数据的”长相”——不做推断,只做描述
为什么要学描述统计 你拿到一张有 100 万行的表,第一件事是什么?不是建模,而是先看看数据长什么样
描述统计就是你的”数据体检报告”
集中趋势告诉你”平均水平在哪”
离散程度告诉你”数据有多散”
分布形态告诉你”数据是对称的还是偏的”
可视化让你一眼看出 文字和数字说不清的规律
做安全分析:流量数据的均值和方差能帮你划异常阈值
做大数据:EDA(探索性数据分析)是建模前的必经之路
做后端:性能指标的 P99/P95 就是分位数的直接应用
核心概念一:集中趋势 直觉比喻 一群人站在操场上,”集中趋势”回答的是——他们大概站在哪个区域?
均值(Mean) 所有值加起来除以个数
$$\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i$$
优点:用到了所有数据
缺点:极端值敏感 ——一个亿万富翁进村,全村”平均”变富翁
排好序后最中间那个值
奇数个取中间,偶数个取中间两个的平均
优点:不受极端值影响
缺点:没用到所有数据的信息
众数(Mode) 出现次数最多的值
可以有多个众数(双峰分布)
对分类数据特别有用(比如”最常见的攻击类型”)
三者的关系 对称分布:均值 ≈ 中位数 ≈ 众数
右偏分布:众数 < 中位数 < 均值
左偏分布:均值 < 中位数 < 众数
Python 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 import numpy as npfrom scipy import statsdata = [23 , 25 , 28 , 30 , 31 , 31 , 35 , 42 , 180 ] mean_val = np.mean(data) median_val = np.median(data) mode_val = stats.mode(data, keepdims=True ).mode[0 ] print (f"均值: {mean_val:.2 f} " )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 npdata = np.array([23 , 25 , 28 , 30 , 31 , 31 , 35 , 42 , 55 ]) range_val = np.ptp(data) q1, q3 = np.percentile(data, [25 , 75 ]) iqr = q3 - q1 variance = np.var(data, 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:.2 f} " )print (f"标准差: {std_dev:.2 f} " )print (f"变异系数: {cv:.1 f} %" )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 statsimport numpy as npincome = np.random.lognormal(mean=10 , sigma=1 , size=10000 ) skew = stats.skew(income) kurt = stats.kurtosis(income) print (f"偏度: {skew:.3 f} " ) print (f"峰度: {kurt:.3 f} " )
核心概念四:可视化工具箱 直方图(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 pltimport numpy as npplt.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):.1 f} ' ) plt.axvline(np.median(data), color='green' , linestyle='--' , label=f'中位数={np.median(data):.1 f} ' ) 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 pltimport numpy as npservice_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 pltimport numpy as npnp.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 ]:.1 f} x+{z[1 ]:.1 f} ' ) 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 snsimport pandas as pdimport numpy as npimport matplotlib.pyplot as pltnp.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 npfrom scipy import statscpu = 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:.4 f} " )print (f"p值: {p_value:.6 f} " )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 pdimport numpy as npnp.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:.2 f} " ) print ("\n偏度和峰度:" )for col in df.select_dtypes(include=[np.number]).columns:print (f" {col} : 偏度={df[col].skew():.3 f} , 峰度={df[col].kurtosis():.3 f} " )
应用场景 大数据应用 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-概率分布 我们将学习数据背后的概率模型