本文介绍了几个重要的变量相关性的度量,包括皮尔逊相关系数、距离相关性和最大信息系数等,并用简单的代码和示例数据展示了这些度量的适用性对比。
从信号的角度来看,这个世界是一个嘈杂的地方。为了弄清楚所有的事情,我们必须有选择地把注意力集中到有用的信息上。
通过数百万年的自然选择过程,我们人类已经变得非常擅长过滤背景信号。我们学会将特定的信号与特定的事件联系起来。
例如,假设你正在繁忙的办公室中打乒乓球。为了回击对手的击球,你需要进行大量复杂的计算和判断,将多个相互竞争的感官信号考虑进去。为了预测球的运动,你的大脑必须重复采样球的位置并估计它未来的轨迹。更厉害的球员还会将对手击球时施加的旋转考虑进去。最后,为了击球,你需要考虑对手的位置、自己的位置、球的速度,以及你打算施加的旋转。
所有这些都涉及到了大量的潜意识微分学。一般来说,我们理所当然的认为,我们的神经系统可以自动做到这些(至少经过一些练习之后)。
同样令人印象深刻的是,人类大脑是如何区别对待它所接收到的无数竞争信号的重要性的。例如,球的位置被认为比你身后发生的对话或你面前打开的门更重要。
这听起来似乎不值得一提,但实际上这证明了可以多大程度上学习从噪声数据中做出准确预测。
当然,一个被给予连续的视听数据流的空白状态机将会面临一个困难的任务,即确定哪些信号能够最好地预测最佳行动方案。
幸运的是,有统计和计算方法可以用来识别带噪声和复杂的数据中的模式。
一、相关性
一般来说,当我们谈到两个变量之间的「相关性(correlation)」时,在某种意义上,我们是指它们的「关系(relatedness)」。
相关变量是包含彼此信息的变量。两个变量的相关性越强,其中一个变量告诉我们的关于另一个变量的信息就越多。
你可能之前就看过:正相关、零相关、负相关
你可能已经对相关性、它的作用和它的局限性有了一定了解。事实上,这是一个数据科学的老生常谈:「相关性不意味着因果关系」
这当然是正确的——有充分的理由说明,即使是两个变量之间有强相关性也不保证存在因果关系。观察到的相关性可能是由于隐藏的第三个变量的影响,或者完全是偶然的。
也就是说,相关性确实允许基于另一个变量来预测一个变量。有几种方法可以用来估计线性和非线性数据的相关性。我们来看看它们是如何工作的。
我们将用 Python 和 R 来进行数学和代码实现。本文示例的代码可以在这里找到:
GitHub 地址:
https://gist.github.com/anonymous/fabecccf33f9c3feb568384f626a2c07
二、皮尔逊相关系数
皮尔逊相关系数(PCC, 或者 Pearson's r)是一种广泛使用的线性相关性的度量,它通常是很多初级统计课程的第一课。从数学角度讲,它被定义为「两个向量之间的协方差,通过它们标准差的乘积来归一化」。
两个成对的向量之间的协方差是它们在均值上下波动趋势的一种度量。也就是说,衡量一对向量是否倾向于在各自平均值的同侧或相反。
让我们看看在 Python 中的实现:
def mean(x):
return sum(x)/len(x)
def covariance(x,y):
calc = []
for i in range(len(x)):
xi = x[i] - mean(x)
yi = y[i] - mean(y)
calc.append(xi * yi)
return sum(calc)/(len(x) - 1)
a = [1,2,3,4,5] ; b = [5,4,3,2,1]
print(covariance(a,b))
协方差的计算方法是从每一对变量中减去各自的均值。然后,将这两个值相乘。
如果都高于(或都低于)均值,那么结果将是一个正数,因为正数 × 正数 = 正数;同样的,负数 × 负数 = 负数。
如果在均值的不同侧,那么结果将是一个负数(因为正数 × 负数 = 负数)。
一旦我们为每一对变量都计算出这些值,将它们加在一起,并除以 n-1,其中 n 是样本大小。这就是样本协方差。
如果这些变量都倾向于分布在各自均值的同一侧,协方差将是一个正数;反之,协方差将是一个负数。这种倾向越强,协方差的绝对值就越大。
如果不存在整体模式,那么协方差将会接近于零。这是因为正值和负值会相互抵消。
最初,协方差似乎是两个变量之间「关系」的充分度量。但是,请看下面的图:
协方差 = 0.00003
看起来变量之间有很强的关系,对吧?那为什么协方差这么小呢(大约是 0.00003)?
这里的关键是要认识到协方差是依赖于比例的。看一下 x 和 y 坐标轴——几乎所有的数据点都落在了 0.015 和 0.04 之间。协方差也将接近于零,因为它是通过从每个个体观察值中减去平均值来计算的。
为了获得更有意义的数字,归一化协方差是非常重要的。方法是将其除以两个向量标准差的乘积。
在希腊字母中 ρ 常用来表示皮尔逊相关系数
在 Python 中:
import math
def stDev(x):
variance = 0
for i in x:
variance += (i - mean(x) ** 2) / len(x)
return math.sqrt(variance)
def Pearsons(x,y):
cov = covariance(x,y)
return cov / (stDev(x) * stDev(y))
这样做的原因是因为向量的标准差是是其方差的平方根。这意味着如果两个向量是相同的,那么将它们的标准差相乘就等于它们的方差。
有趣的是,两个相同向量的协方差也等于它们的方差。
因此,两个向量之间协方差的最大值等于它们标准差的乘积(当向量完全相关时会出现这种情况)。这将相关系数限制在 -1 到 +1 之间。
箭头指向哪个方向?
顺便说一下,一个定义两个向量的 PCC 的更酷的方法来自线性代数。
首先,我们通过从向量各自的值中减去其均值的方法来「集中」向量。
a = [1,2,3,4,5] ; b = [5,4,3,2,1]
a_centered = [i - mean(a) for i in a]
b_centered = [j - mean(b) for j in b]
现在,我们可以利用向量可以看做指向特定方向的「箭头」的事实。
例如,在 2-D 空间中,向量 [1,3] 可以代表一个沿 x 轴 1 个单位,沿 y 轴 3 个单位的箭头。同样,向量 [2,1] 可以代表一个沿 x 轴 2 个单位,沿 y 轴 1 个单位的箭头。
两个向量 (1,3) 和 (2,1) 如箭头所示。
类似地,我们可以将数据向量表示为 n 维空间中的箭头(尽管当 n > 3 时不能尝试可视化)。
这些箭头之间的角度 ϴ 可以使用两个向量的点积来计算。定义为:
或者,在 Python 中:
def dotProduct(x,y):
calc = 0
for i in range(len(x)):
calc += x[i] * y[i]
return calc
点积也可以被定义为:
其中 || x || 是向量 x 的大小(或「长度」)(参考勾股定理),ϴ 是箭头向量之间的角度。
正如一个 Python 函数:
def magnitude(x):
x_sq = [i ** 2 for i in x]
return math.sqrt(sum(x_sq))
我们通过将点积除以两个向量大小的乘积的方法得到 cos(ϴ)。
def cosTheta(x,y):
mag_x = magnitude(x)
mag_y = magnitude(y)
return dotProduct(x,y) / (mag_x * mag_y)
现在,如果你对三角学有一定了解,你可能会记得,余弦函数产生一个在 +1 和 -1 之间震荡的图形。
cos(ϴ) 的值将根据两个箭头向量之间的角度而发生变化。
当角度为零时(即两个向量指向完全相同的方向),cos(ϴ) 等于 1。
当角度为 -180°时(两个向量指向完全相反的方向),cos(ϴ) 等于 -1。
当角度为 90°时(两个向量指向完全不相关的方向),cos(ϴ) 等于 0。
这可能看起来很熟悉——一个介于 +1 和 -1 之间的衡量标准似乎描述了两个向量之间的关系?那不是 Pearson’s r 吗?
那么——这正是它的解释!通过将数据视为高维空间中的箭头向量,我们可以用它们之间的角度 ϴ 作为相似度的衡量。
A) 正相关向量;B) 负相关向量;C) 不相关向量
该角度 ϴ 的余弦在数学上与皮尔逊相关系数相等。当被视为高维箭头时,正相关向量将指向一个相似的方向。负相关向量将指向相反的方向。而不相关向量将指向直角。
就我个人而言,我认为这是一个理解相关性的非常直观的方法。
统计显著性?
正如频率统计一样,重要的是询问从给定样本计算的检验统计量实际上有多重要。Pearson's r 也不例外。
不幸的是,PCC 估计的置信区间不是完全直接的。
这是因为 Pearson's r 被限制在 -1 和 +1 之间,因此不是正态分布的。而估计 PCC,例如 +0.95 之上只有很少的容错空间,但在其之下有大量的容错空间。
幸运的是,有一个解决方案——用一个被称为 Fisher 的 Z 变换的技巧:
像平常一样计算 Pearson's r 的估计值。
用 Fisher 的 Z 变换将 r→z,用公式 z = arctanh(r) 完成。
现在计算 z 的标准差。幸运的是,这很容易计算,由 SDz = 1/sqrt(n-3) 给出,其中 n 是样本大小。
选择显著性阈值,alpha,并检查与此对应的平均值有多少标准差。如果取 alpha = 0.95,用 1.96。
通过计算 z +(1.96 × SDz) 找到上限,通过计算 z - (1.96 × SDz) 找到下限。
用 r = tanh(z) 将这些转换回 r。
如果上限和下限都在零的同一侧,则有统计显著性!
这里是在 Python 中的实现:
r = Pearsons(x,y)
z = math.atanh(r)
SD_z = 1 / math.sqrt(len(x) - 3)
z_upper = z + 1.96 * SD_z
z_lower = z - 1.96 * SD_z
r_upper = math.tanh(z_upper)
r_lower = math.tanh(z_lower)
当然,当给定一个包含许多潜在相关变量的大数据集时,检查每对的相关性可能很吸引人。这通常被称为「数据疏浚」——在数据集中查找变量之间的任何明显关系。
如果确实采用这种多重比较方法,则应该用适当的更严格的显著性阈值来降低发现错误相关性的风险(即找到纯粹偶然相关的无关变量)。
一种方法是使用 Bonferroni correction。
小结
到现在为止还好。我们已经看到 Pearson's r 如何用来计算两个变量之间的相关系数,以及如何评估结果的统计显著性。给定一组未知的数据,用于开始挖掘变量之间的重要关系是很有可能的。
但是,有一个重要的陷阱——Pearson's r 只适用于线性数据。
看下面的图。它们清楚地展示了一种看似非随机的关系,但是 Pearson's r 非常接近于零。
原因是因为这些图中的变量具有非线性关系。
我们通常可以将两个变量之间的关系描绘成一个点云,分散在一条线的两侧。点云的分散度越大,数据越「嘈杂」,关系越弱。
然而,由于它将每个单独的数据点与整体平均值进行比较,所以 Pearson's r 只考虑直线。这意味着检测非线性关系并不是很好。
在上面的图中,Pearson's r 并没有显示研究对象的相关性。
然而,这些变量之间的关系很显然是非随机的。幸运的是,我们有不同的相关性方法。
让我们来看看其中几个。
三、距离相关性
有一个误解存在了10年:仅基于它们都利用相同的基本概率概念这一事实,来混淆这两个术语是不合理的。
距离相关性与 Pearson's r 有一些相似之处,但是实际上是用一个相当不同的协方差概念来计算的。该方法通过用「距离」类似物替代常用的协方差和标准差(如上所定义)的概念。
类似 Pearson's r,「距离相关性」被定义为「距离协方差」,由「距离标准差」来归一化。
距离相关性不是根据它们与各自平均值的距离来估计两个变量如何共同变化,而是根据与其他点的距离来估计它们是如何共同变化的,从而能更好捕捉变量之间非线性依赖关系。
深入细节
出生于 1773 年的 Robert Brown 是一名苏格兰植物学家。当布朗在显微镜下研究植物花粉时,注意到液面上有随机运动的有机颗粒。
他没有想到,这一观察竟使他名垂千古——他成为了布朗运动的(重新)发现者。
他更不会知道,近一个世纪的时间后爱因斯坦才对这种现象做出了解释,从而证实了原子的存在。同年,爱因斯坦发表了关于狭义相对论的论文(E=MC²),并打开了量子理论的大门。
布朗运动是这样一个物理过程:由于与周围粒子的碰撞,微小粒子随机运动。
布朗运动背后的数学原理可以被推广为维纳过程(Weiner process),维纳过程在数学金融中最著名的模型 Black-Scholes 中也扮演着重要的角色。
有趣的是,Gabor Szekely 在 20 世纪中期的研究表明,布朗运动和维纳过程和一个非线性关联度量相关。
让我们来看看如何由长度为 N 的向量 x 和 y 计算这个量。
1. 首先,我们对每个向量构建 N×N 的距离矩阵。距离矩阵和地图中的道路距离表非常类似——每行、每列的交点显示了相应城市间的距离。在距离矩阵中,行 i 和列 j 的交点给出了向量的第 i 个元素和第 j 个元素之间的距离。
2. 第二,矩阵是「双中心」的。也就是说,对于每个元素,我们减去了它的行平均值和列平均值。然后,我们再加上整个矩阵的总平均值。
上述公式中,加「^」表示「双中心」,加「-」表示「平均值」。
3. 在两个双中心矩阵的基础上,将 X 中每个元素的均值乘以 Y 中相应元素的均值,则可计算出距离协方差的平方。
4. 现在,我们可以用类似的办法找到「距离方差」。请记住,若两个向量相同,其协方差与其方差相等。因此,距离方差可表示如下:
5. 最后,我们利用上述公式计算距离相关性。请记住,(距离)标准差与(距离)方差的平方根相等。
如果你更喜欢代码实现而非数学符号,那么请看下面的 R 语言实现:
set.seed(1234)
doubleCenter <- function(x){
centered <- x
for(i in 1:dim(x)[1]){
for(j in 1:dim(x)[2]){
centered[i,j] <- x[i,j] - mean(x[i,]) - mean(x[,j]) + mean(x)
}
}
return(centered)
}
distanceCovariance <- function(x,y){
N <- length(x)
distX <- as.matrix(dist(x))
distY <- as.matrix(dist(y))
centeredX <- doubleCenter(distX)
centeredY <- doubleCenter(distY)
calc <- sum(centeredX * centeredY)
return(sqrt(calc/(N^2)))
}
distanceVariance <- function(x){
return(distanceCovariance(x,x))
}
distanceCorrelation <- function(x,y){
cov <- distanceCovariance(x,y)
sd <- sqrt(distanceVariance(x)*distanceVariance(y))
return(cov/sd)
}
# Compare with Pearson's r
x <- -10:10
y <- x^2 + rnorm(21,0,10)
cor(x,y) # --> 0.057
distanceCorrelation(x,y) # --> 0.509
任意两变量的距离相关性都在 0 和 1 之间。其中,0 代表两变量相互独立,而接近于 1 则表明变量间存在依赖关系。
如果你不想从头开始编写距离相关方法,你可以安装 R 语言的 energy
包(https://cran.r-project.org/web/packages/energy/index.html),设计此方案的研究者提供了本代码。在该程序包中,各类可用方案调用的是
C 语言编写的函数,因此有着很大的速度优势。