统计量¶
统计量用单个数值概括数据,捕捉其离散程度、位置、形状和关联。本节涵盖方差、标准差、四分位数、偏度、峰度、协方差、相关和 z 分数——这是探索性数据分析和机器学习特征工程的基础工具集。
-
在上一节中,我们介绍了矩作为一组概括性统计量家族。在此,我们展开讨论从矩中衍生出的实用工具:度量离散程度、位置、形状和关联的统计量。
-
离散程度回答了这样一个问题:数据的分布有多分散?两个班级的平均考试成绩可能相同,但其分散程度却可能大相径庭。
-
窄(蓝色)分布的方差较小:大部分数值紧密聚集在均值周围。宽(红色)分布的方差较大:数值散布得更远。
-
方差是距均值距离的平方的平均值。取平方是为了避免正负偏差相互抵消。
- 当处理样本(而非整个总体)时,我们用 \(N - 1\) 而不是 \(N\) 来除。这种修正(称为贝塞尔校正)是因为样本往往会低估真实的变异性:
-
标准差是方差的平方根:\(\sigma = \sqrt{\sigma^2}\)。它将度量单位恢复为原始单位。如果数据的单位是厘米,方差的单位是 cm\(^2\),而标准差的单位又回到了 cm。
-
平均绝对偏差(MAD)是一个更简单的替代方案。它不取平方,而是取每个偏差的绝对值:
-
MAD 对方差而言对异常值更稳健,因为它不会通过平方来放大大的偏差。然而,方差在数学上更便利(在证明和机器学习优化中更容易分解)。
-
位置回答了一个不同的问题:特定数值相对于其余数据的位置在哪里?
-
四分位数将排序后的数据分成四个相等的部分。Q1(第 25 百分位数)是低于该值的数据占 25% 的值。Q2 是中位数(第 50 百分位数)。Q3 是第 75 百分位数。
-
四分位距(IQR)是 \(Q3 - Q1\)。它捕捉了中间 50% 数据的离散程度,排除了极端值。
-
箱线图是统计学中最有用的可视化工具之一。箱体从 Q1 延伸到 Q3,中间的线为中位数,须线延伸到最远的非异常值,而须线之外的点则为异常值。
-
百分位数是四分位数的推广。第 \(p\) 百分位数是低于该值的观测值占 \(p\%\) 的值。Q1 是第 25 百分位数,中位数是第 50 百分位数,Q3 是第 75 百分位数。
-
z 分数告诉你一个值距均值有多少个标准差:
-
z 分数为 2 表示该值高于均值 2 个标准差。z 分数为 \(-1.5\) 表示低于均值 1.5 个标准差。这也称为标准化,在机器学习中广泛用于特征缩放,因为它将任何分布变换为均值为 0、标准差为 1。
-
形状描述了分布超出其中心和离散程度之外的几何特征。
-
偏度(上一节中的标准化三阶矩)衡量不对称性。像正态曲线这样完全对称的分布,其偏度为零。正偏度表示右尾较长(如收入分布)。负偏度表示左尾较长(如退休年龄分布)。
- 峰度(标准化四阶矩)衡量尾部厚度。正态分布的峰度为 3。尾部更厚(更容易出现异常值)的分布的峰度大于 3。
- 相关衡量两个变量之间关系的强度和方向。它回答了:当一个变量上升时,另一个变量倾向于上升、下降,还是基本不变?
- 皮尔森相关(\(r\))衡量线性关联。其取值范围从 \(-1\)(完全负相关)到 \(0\)(无相关)再到 \(+1\)(完全正相关)。
-
如果你还记得第 1 章中的点积,皮尔森相关本质上就是 \(\mathbf{x}\) 和 \(\mathbf{y}\) 均值中心化之后的余弦相似度。
-
斯皮尔曼相关(\(\rho\))衡量单调关联。它不使用原始值,而是先对它们进行排序,然后在排序上计算皮尔森相关。这使得它对异常值稳健,并且即使关系是非线性的,只要是一致递增或递减的,也能正常工作。
-
几何平均数是当数值相互乘除时(如增长率)合适的平均值。如果你的投资分别增长了 10%、20% 和 30%,那么平均增长因子并不是这些增长率的算术平均数。而是:
-
具体到增长率,先将百分比转换为因子(1.10、1.20、1.30),计算几何平均数,再减去 1。
-
指数移动平均(EMA)赋予最近观测值更高的权重。与简单移动平均中窗口内所有点权重相等不同,EMA 呈指数衰减:
-
平滑因子 \(\alpha\)(介于 0 和 1 之间)控制旧观测值失去影响的速度。\(\alpha\) 越大,对近期变化的响应越灵敏;\(\alpha\) 越小,曲线越平滑。在机器学习中,EMA 被用于 Adam 等优化器以及批归一化的运行统计中。
-
异常值检测识别出与其余数据异常遥远的数点。两种常用方法:
- IQR 法:如果一个点低于 \(Q1 - 1.5 \times \text{IQR}\) 或高于 \(Q3 + 1.5 \times \text{IQR}\),则为异常值
- Z 分数法:如果 \(|z| > 3\)(距均值超过 3 个标准差),则为异常值
-
IQR 法更稳健,因为它不假设正态分布。Z 分数法在数据近似正态时效果良好,但当分布高度偏斜时可能失效。
编程练习(使用 CoLab 或 notebook)¶
-
计算数据集的方差、标准差和 MAD,并进行比较。观察添加极端异常值时发生的变化。
import jax.numpy as jnp data = jnp.array([4, 8, 6, 5, 3, 7, 9, 5, 6, 7], dtype=jnp.float32) mean = jnp.mean(data) variance = jnp.var(data) std = jnp.std(data) mad = jnp.mean(jnp.abs(data - mean)) print("原始数据:") print(f" 方差:{variance:.3f},标准差:{std:.3f},MAD:{mad:.3f}") # 添加一个异常值并重新计算 data_outlier = jnp.append(data, 100.0) mean2 = jnp.mean(data_outlier) print(f"\n添加异常值(100)后:") print(f" 方差:{jnp.var(data_outlier):.3f},标准差:{jnp.std(data_outlier):.3f},MAD:{jnp.mean(jnp.abs(data_outlier - mean2)):.3f}") -
计算两个变量之间的皮尔森相关和斯皮尔曼相关。尝试不同的关系。
import jax import jax.numpy as jnp # 完全线性关系 x = jnp.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=jnp.float32) y = 2 * x + 1 # 试试修改这个! def pearson(a, b): a_c = a - jnp.mean(a) b_c = b - jnp.mean(b) return jnp.sum(a_c * b_c) / (jnp.sqrt(jnp.sum(a_c**2)) * jnp.sqrt(jnp.sum(b_c**2))) def spearman(a, b): rank_a = jnp.argsort(jnp.argsort(a)).astype(jnp.float32) rank_b = jnp.argsort(jnp.argsort(b)).astype(jnp.float32) return pearson(rank_a, rank_b) print(f"皮尔森 r: {pearson(x, y):.4f}") print(f"斯皮尔曼 ρ:{spearman(x, y):.4f}") -
分别使用 IQR 和 Z 分数方法实现异常值检测,然后比较它们在偏斜数据上的结果。
import jax.numpy as jnp data = jnp.array([2, 3, 3, 4, 5, 5, 5, 6, 6, 7, 50], dtype=jnp.float32) # IQR 方法 q1, q3 = jnp.percentile(data, 25), jnp.percentile(data, 75) iqr = q3 - q1 lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr iqr_outliers = data[(data < lower) | (data > upper)] print(f"IQR 边界:[{lower:.1f}, {upper:.1f}]") print(f"IQR 异常值:{iqr_outliers}") # Z 分数方法 z_scores = (data - jnp.mean(data)) / jnp.std(data) z_outliers = data[jnp.abs(z_scores) > 3] print(f"\nZ 分数:{z_scores}") print(f"Z 分数异常值(|z| > 3):{z_outliers}") -
在不同平滑因子下计算并绘制带噪声数据的指数移动平均。
import jax.numpy as jnp import matplotlib.pyplot as plt # 生成带噪声的数据 key = __import__("jax").random.PRNGKey(0) noise = __import__("jax").random.normal(key, shape=(50,)) signal = jnp.linspace(0, 5, 50) + noise def ema(data, alpha): result = jnp.zeros_like(data) result = result.at[0].set(data[0]) for t in range(1, len(data)): result = result.at[t].set(alpha * data[t] + (1 - alpha) * result[t - 1]) return result plt.figure(figsize=(10, 4)) plt.plot(signal, "o", alpha=0.3, label="原始数据", color="#999") for alpha, color in [(0.1, "#e74c3c"), (0.3, "#3498db"), (0.7, "#27ae60")]: plt.plot(ema(signal, alpha), label=f"α={alpha}", color=color, linewidth=2) plt.legend() plt.title("不同平滑因子下的 EMA") plt.show()