为什么NUMPY的correlate和corrcoef返回不同值,以及如何在“全”模式下“归一化”correlate?

22 投票
3 回答
26120 浏览
提问于 2025-04-16 15:35

我正在尝试在Python中使用Numpy进行一些时间序列分析。

我有两个中等大小的序列,每个序列有2万条数据,我想检查它们之间的滑动相关性。

使用corrcoef函数,我得到的结果是一个自相关/相关系数的矩阵。对我来说,这个结果本身没有什么用,因为其中一个序列有滞后。

而correlate函数(使用mode="full")返回了一个包含4万个元素的列表,这看起来更像我想要的结果(峰值距离列表中心的距离正好是滞后所指示的),但这些值都很奇怪——有的甚至高达500,而我原本期待的值应该在-1到1之间。

我不能简单地把所有值都除以最大值;我知道最大相关性并不是1。

我该如何规范化“交叉相关”(在“全”模式下的相关性),让返回的值能够反映每个滞后步骤的相关性,而不是那些非常大且奇怪的值呢?

3 个回答

0

full模式下,直接对滞后信号或特征计算corrcoef是否有意义?代码

from dataclasses import dataclass
from typing import Any, Optional, Sequence

import numpy as np

ArrayLike = Any


@dataclass
class XCorr:
    cross_correlation: np.ndarray
    lags: np.ndarray


def cross_correlation(
    signal: ArrayLike, feature: ArrayLike, lags: Optional[Sequence[int]] = None
) -> XCorr:
    """
    Computes normalized cross correlation between the `signal` and the `feature`.
    Current implementation assumes the `feature` can't be longer than the `signal`.
    You can optionally provide specific lags, if not provided `signal` is padded
    with the length of the `feature` - 1, and the `feature` is slid/padded (creating lags)
    with 0 padding to match the length of the new signal. Pearson product-moment
    correlation coefficients is computed for each lag.

    See: https://en.wikipedia.org/wiki/Cross-correlation

    :param signal: observed signal
    :param feature: feature you are looking for
    :param lags: optional lags, if not provided equals to (-len(feature), len(signal))
    """
    signal_ar = np.asarray(signal)
    feature_ar = np.asarray(feature)
    if np.count_nonzero(feature_ar) == 0:
        raise ValueError("Unsupported - feature contains only zeros")
    assert (
        signal_ar.ndim == feature_ar.ndim == 1
    ), "Unsupported - only 1d signal/feature supported"
    assert len(feature_ar) <= len(
        signal
    ), "Unsupported - signal should be at least as long as the feature"
    padding_sz = len(feature_ar) - 1
    padded_signal = np.pad(
        signal_ar, (padding_sz, padding_sz), "constant", constant_values=0
    )
    lags = lags if lags is not None else range(-padding_sz, len(signal_ar), 1)
    if np.max(lags) >= len(signal_ar):
        raise ValueError("max positive lag must be shorter than the signal")
    if np.min(lags) <= -len(feature_ar):
        raise ValueError("max negative lag can't be longer than the feature")
    assert np.max(lags) < len(signal_ar), ""
    lagged_patterns = np.asarray(
        [
            np.pad(
                feature_ar,
                (padding_sz + lag, len(signal_ar) - lag - 1),
                "constant",
                constant_values=0,
            )
            for lag in lags
        ]
    )
    return XCorr(
        cross_correlation=np.corrcoef(padded_signal, lagged_patterns)[0, 1:],
        lags=np.asarray(lags),
    )

举个例子:

signal = [0, 0, 1, 0.5, 1, 0, 0, 1]
feature = [1, 0, 0, 1]
xcorr = cross_correlation(signal, feature)
assert xcorr.lags[xcorr.cross_correlation.argmax()] == 4
0

根据这个幻灯片,我建议可以这样做:

def cross_correlation(a1, a2):
        lags = range(-len(a1)+1, len(a2))
        cs = []
        for lag in lags:
            idx_lower_a1 = max(lag, 0)
            idx_lower_a2 = max(-lag, 0)
            idx_upper_a1 = min(len(a1), len(a1)+lag)
            idx_upper_a2 = min(len(a2), len(a2)-lag)
            b1 = a1[idx_lower_a1:idx_upper_a1]
            b2 = a2[idx_lower_a2:idx_upper_a2]
            c = np.correlate(b1, b2)[0]
            c = c / np.sqrt((b1**2).sum() * (b2**2).sum())
            cs.append(c)
        return cs
28

你在寻找归一化的互相关。这种功能在Numpy中还没有,但有一个补丁正在等待审核,正好满足你的需求。我觉得应用这个补丁应该不难。大部分补丁内容都是文档说明,真正增加的代码行只有:

if normalize:
    a = (a - mean(a)) / (std(a) * len(a))
    v = (v - mean(v)) /  std(v)

这里的a和v是你输入的Numpy数组,用来计算互相关。把这些代码加到你自己的Numpy版本里应该不难,或者你也可以直接复制一下相关函数,把这些代码加进去。如果是我,我会选择后者。

另外,还有一个可能更好的方法,就是在把数据传给互相关函数之前,先对输入的向量进行归一化。你可以根据自己的需要选择哪种方式。

顺便说一下,根据维基百科关于互相关的页面,这似乎是正确的归一化方式,只是分母用的是len(a)而不是(len(a)-1)。我觉得这个差异就像样本标准差和总体标准差的区别,实际上不会有什么太大影响。

撰写回答