统计度量 统计度量用单个数字总结数据,刻画其离散度、位置、形状和关联。本文件涵盖方差、标准差、四分位数、偏度、峰度、协方差、相关性和 z 分数,这是探索性数据分析与 ML 特征工程(feature engineering)的工具箱。 在上一个文件里,我们把矩作为一整族汇总统计量做了介绍。这里我们展开从矩衍生出的实用工具:离散度、位置、形状和关联的度量。 离散度(dispersion)回答的问题是:数据有多分散?两个班级可能有相同的平均分,但离散程度却天差地别。 两个分布均值相同,但离散程度不同 窄(蓝色)的分布方差低:大多数值紧紧聚在均值附近。宽(红色)的分布方差高:数值散布得更远。 方差(variance)是到均值的平均平方距离。之所以平方,是为了避免正负偏离相互抵消。
统计度量用单个数字总结数据,刻画其离散度、位置、形状和关联。本文件涵盖方差、标准差、四分位数、偏度、峰度、协方差、相关性和 z 分数,这是探索性数据分析与 ML 特征工程(feature engineering)的工具箱。
在上一个文件里,我们把矩作为一整族汇总统计量做了介绍。这里我们展开从矩衍生出的实用工具:离散度、位置、形状和关联的度量。
**离散度(dispersion)**回答的问题是:数据有多分散?两个班级可能有相同的平均分,但离散程度却天差地别。
窄(蓝色)的分布方差低:大多数值紧紧聚在均值附近。宽(红色)的分布方差高:数值散布得更远。
**方差(variance)**是到均值的平均平方距离。之所以平方,是为了避免正负偏离相互抵消。
**标准差(standard deviation)**是方差的平方根:\sigma = \sqrt{\sigma^2}。它把这个度量拉回到原始单位。如果你的数据单位是厘米,方差单位是 cm^2,但标准差又回到了 cm。
**平均绝对偏差(Mean Absolute Deviation,MAD)**是一个更简单的替代方案。它不平方,而是取每个偏离的绝对值:
因为 MAD 不会通过平方放大较大的偏离,所以它比方差对离群点更稳健。不过方差在数学上更方便(它在证明和 ML 优化中能很优雅地分解)。
**位置(position)**回答的是另一个问题:某个具体数值相对于其他数据处于什么位置?
**四分位数(quartiles)**把排序后的数据分成四个相等的部分。Q1(第 25 百分位数)是低于它就有 25% 数据的那个值。Q2 是中位数(第 50 百分位数)。Q3 是第 75 百分位数。
**四分位距(Interquartile Range,IQR)**是 Q3 - Q1。它刻画的是中间 50% 数据的离散范围,忽略了两端的极端值。
箱线图是统计学中最有用的可视化之一。箱子从 Q1 延伸到 Q3,箱子里的线是中位数,须延伸到最远的非离群点,而须之外的点就是离群点。
**百分位数(percentiles)**是四分位数的推广。第 p 百分位数是低于它就有 p\% 观测值的那个值。Q1 是第 25 百分位数,中位数是第 50,Q3 是第 75。
**z 分数(z-score)**告诉你某个值偏离了均值几个标准差:
z 分数为 2 表示该值比均值高 2 个标准差。z 分数为 -1.5 表示比均值低 1.5 个标准差。这也叫做标准化(standardisation),在 ML 的特征缩放中被大量使用,因为它能把任何分布变换成均值为 0、标准差为 1 的分布。
**形状(shape)**描述的是分布在中心和离散度之外的几何形态。
偏度(skewness)(上一个文件中标准化的三阶矩)度量不对称性。像正态曲线那样完全对称的分布,偏度为零。偏度为正意味着右尾更长(例如收入分布)。偏度为负意味着左尾更长(例如退休年龄)。
如果你记得第 1 章的点积,皮尔逊相关本质上就是 \mathbf{x} 和 \mathbf{y} 去均值(mean-centred)版本之间的余弦相似度。
斯皮尔曼相关(Spearman correlation)(\rho)度量的是单调关联。它不直接用原始值,而是先把它们转换成排名,再对排名计算皮尔逊相关。这使它对离群点很稳健,并且即使关系是非线性的,只要它是稳定地递增或递减,它也能起作用。
**几何平均(geometric mean)**适用于数值相乘的情形,比如增长率。如果你的投资分别增长 10%、20%、30%,那么平均增长因子并不是这些比率的算术平均。正确的做法是:
对于增长率,要先把百分比换算成因子(1.10、1.20、1.30),算出几何平均,再减去 1。
**指数移动平均(Exponential Moving Average,EMA)**给最近的观测赋予更大的权重。与窗口内所有点权重相等的简单移动平均不同,EMA 按指数衰减:
平滑系数 \alpha(介于 0 和 1 之间)控制旧观测失去影响力的速度。\alpha 越大,对近期变化越敏感;\alpha 越小,越平滑。在 ML 中,EMA 被用于 Adam 等优化器以及批归一化(batch normalisation)的滑动统计量。
**离群点检测(outlier detection)**识别那些离其他数据异常远的数据点。两种常用方法:
IQR 方法更稳健,因为它不假设数据服从正态分布。z 分数方法在数据近似正态时效果很好,但当分布严重偏斜时可能会失效。
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("Original data:") print(f" Variance: {variance:.3f}, Std: {std:.3f}, MAD: {mad:.3f}") # 加入一个离群点并重新计算 data_outlier = jnp.append(data, 100.0) mean2 = jnp.mean(data_outlier) print(f"\nWith outlier (100):") print(f" Variance: {jnp.var(data_outlier):.3f}, Std: {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"Pearson r: {pearson(x, y):.4f}") print(f"Spearman ρ: {spearman(x, y):.4f}")
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 bounds: [{lower:.1f}, {upper:.1f}]") print(f"IQR outliers: {iqr_outliers}") # z 分数方法 z_scores = (data - jnp.mean(data)) / jnp.std(data) z_outliers = data[jnp.abs(z_scores) > 3] print(f"\nZ-scores: {z_scores}") print(f"Z-score outliers (|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="raw data", 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 with different smoothing factors") plt.show()