Como posso usar numpy.correlate para fazer autocorrelação?

107

Eu preciso fazer a autocorrelação de um conjunto de números, que pelo que entendi é apenas a correlação do conjunto consigo mesmo.

Eu tentei usando a função correlate de numpy, mas não acredito no resultado, pois quase sempre dá um vetor onde o primeiro número não é o maior, como deveria ser.

Então, esta questão é realmente duas questões:

  1. O que exatamente está numpy.correlatefazendo?
  2. Como posso usá-lo (ou outra coisa) para fazer a autocorrelação?
Ben
fonte
Consulte também: stackoverflow.com/questions/12269834/… para obter informações sobre autocorrelação normalizada.
amcnabb de

Respostas:

115

Para responder à sua primeira pergunta, numpy.correlate(a, v, mode)é realizar a convolução de acom o reverso de ve dar os resultados recortados pelo modo especificado. A definição de convolução , C (t) = ∑ -∞ <i <∞ a i v t + i onde -∞ <t <∞, permite resultados de -∞ a ∞, mas obviamente você não pode armazenar um infinitamente longo array. Portanto, ele deve ser cortado, e é aí que entra o modo. Existem 3 modos diferentes: completo, igual e válido:

  • o modo "completo" retorna resultados para todos os tlocais em que ambos ae vtêm alguma sobreposição.
  • "mesmo" modo retorna um resultado com o mesmo comprimento do vetor mais curto ( aou v).
  • "válidos" modo retorna resultados apenas quando ae vcompletamente sobrepõem uns aos outros. A documentação para numpy.convolvedá mais detalhes sobre os modos.

Para a sua segunda pergunta, acho que numpy.correlate está dando a autocorrelação, está apenas dando um pouco mais também. A autocorrelação é usada para descobrir o quão similar um sinal, ou função, é consigo mesmo em uma certa diferença de tempo. Em uma diferença de tempo de 0, a autocorrelação deve ser a mais alta porque o sinal é idêntico a ele mesmo, então você esperava que o primeiro elemento na matriz de resultado de autocorrelação fosse o maior. No entanto, a correlação não começa em uma diferença de tempo de 0. Ela começa em uma diferença de tempo negativa, fecha em 0 e, em seguida, torna-se positiva. Ou seja, você estava esperando:

autocorrelação (a) = ∑ -∞ <i <∞ a i v t + i onde 0 <= t <∞

Mas o que você conseguiu foi:

autocorrelação (a) = ∑ -∞ <i <∞ a i v t + i onde -∞ <t <∞

O que você precisa fazer é pegar a última metade do resultado da correlação, e essa deve ser a autocorrelação que você está procurando. Uma função python simples para fazer isso seria:

def autocorr(x):
    result = numpy.correlate(x, x, mode='full')
    return result[result.size/2:]

É claro que você precisará da verificação de erros para ter certeza de que xé realmente um array 1-d. Além disso, essa explicação provavelmente não é a mais matematicamente rigorosa. Tenho discutido infinitos porque a definição de convolução os usa, mas isso não se aplica necessariamente à autocorrelação. Portanto, a parte teórica desta explicação pode ser um pouco confusa, mas espero que os resultados práticos sejam úteis. Essas páginas sobre autocorrelação são muito úteis e podem fornecer uma base teórica muito melhor se você não se importar em percorrer a notação e conceitos pesados.

A. Levy
fonte
6
Nas compilações atuais do numpy, o modo 'mesmo' pode ser especificado para atingir exatamente o que A. Levy propôs. O corpo da função poderia ser lidoreturn numpy.correlate(x, x, mode='same')
David Zwicker
13
@DavidZwicker mas os resultados são diferentes! np.correlate(x,x,mode='full')[len(x)//2:] != np.correlate(x,x,mode='same'). Por exemplo, x = [1,2,3,1,2]; np.correlate(x,x,mode='full');{ >>> array([ 2, 5, 11, 13, 19, 13, 11, 5, 2])} np.correlate(x,x,mode='same');{ >>> array([11, 13, 19, 13, 11])}. O correto é: np.correlate(x,x,mode='full')[len(x)-1:];{ >>> array([19, 13, 11, 5, 2])} veja que o primeiro item é o maior .
Desenvolvedor
19
Observe que esta resposta fornece a autocorrelação não normalizada.
amcnabb de
4
Acho que @Developer dá o corte correto: [len(x)-1:]começa no 0-lag. Como o fullmodo fornece um tamanho de resultado 2*len(x)-1, A.Levy's [result.size/2:]é o mesmo que [len(x)-1:]. É melhor torná-lo um int, no entanto [result.size//2:].
Jason
Descobri que deve ser um int, pelo menos em python 3.7
kevinkayaks
25

A autocorrelação vem em duas versões: estatística e convolução. Ambos fazem o mesmo, exceto por um pequeno detalhe: A versão estatística é normalizada para estar no intervalo [-1,1]. Aqui está um exemplo de como você faz o estatístico:

def acf(x, length=20):
    return numpy.array([1]+[numpy.corrcoef(x[:-i], x[i:])[0,1]  \
        for i in range(1, length)])
Jonathf
fonte
9
Você quer numpy.corrcoef[x:-i], x[i:])[0,1]a segunda linha, pois o valor de retorno de corrcoefé uma matriz 2x2
luispedro
Qual é a diferença entre as autocorrelações estatísticas e convolucionais?
Daniel diz Restabelecer Monica
1
@DanielPendergast: A segunda frase responde que: Ambos fazem o mesmo, exceto por um pequeno detalhe: O primeiro [estatístico] é normalizado para estar no intervalo [-1,1]
n1k31t4
21

Use a numpy.corrcoeffunção em vez de numpy.correlatepara calcular a correlação estatística para um atraso de t:

def autocorr(x, t=1):
    return numpy.corrcoef(numpy.array([x[:-t], x[t:]]))
Ramón J Romero e Vigil
fonte
Os "coeficientes de correlação" não se referem à autocorrelação usada no processamento do sinal e não à autocorrelação usada nas estatísticas? en.wikipedia.org/wiki/Autocorrelation#Signal_processing
Daniel diz Reintegrar Monica em
@DanielPendergast Não estou tão familiarizado com o processamento de sinais. Dos documentos numpy: "Retorne os coeficientes de correlação do momento do produto Pearson.". Essa é a versão de processamento de sinal?
Ramón J Romero e Vigil
18

Acho que há 2 coisas que confundem este tópico:

  1. definição estatística vs processamento de sinal: como outros apontaram, na estatística normalizamos a autocorrelação em [-1,1].
  2. média / variância parcial vs não parcial: quando a série temporal muda com um atraso> 0, o tamanho da sobreposição será sempre <comprimento original. Usamos a média e o padrão do original (não parcial) ou sempre calculamos uma nova média e o padrão usando a sobreposição em constante mudança (parcial) faz a diferença. (Provavelmente existe um termo formal para isso, mas vou usar "parcial" por enquanto).

Eu criei 5 funções que calculam a autocorrelação de um array 1d, com distinções parciais versus não parciais. Alguns usam fórmulas de estatísticas, outros usam correlato no sentido de processamento de sinal, o que também pode ser feito via FFT. Mas todos os resultados são autocorrelações na definição de estatísticas , portanto, ilustram como estão vinculados uns aos outros. Código abaixo:

import numpy
import matplotlib.pyplot as plt

def autocorr1(x,lags):
    '''numpy.corrcoef, partial'''

    corr=[1. if l==0 else numpy.corrcoef(x[l:],x[:-l])[0][1] for l in lags]
    return numpy.array(corr)

def autocorr2(x,lags):
    '''manualy compute, non partial'''

    mean=numpy.mean(x)
    var=numpy.var(x)
    xp=x-mean
    corr=[1. if l==0 else numpy.sum(xp[l:]*xp[:-l])/len(x)/var for l in lags]

    return numpy.array(corr)

def autocorr3(x,lags):
    '''fft, pad 0s, non partial'''

    n=len(x)
    # pad 0s to 2n-1
    ext_size=2*n-1
    # nearest power of 2
    fsize=2**numpy.ceil(numpy.log2(ext_size)).astype('int')

    xp=x-numpy.mean(x)
    var=numpy.var(x)

    # do fft and ifft
    cf=numpy.fft.fft(xp,fsize)
    sf=cf.conjugate()*cf
    corr=numpy.fft.ifft(sf).real
    corr=corr/var/n

    return corr[:len(lags)]

def autocorr4(x,lags):
    '''fft, don't pad 0s, non partial'''
    mean=x.mean()
    var=numpy.var(x)
    xp=x-mean

    cf=numpy.fft.fft(xp)
    sf=cf.conjugate()*cf
    corr=numpy.fft.ifft(sf).real/var/len(x)

    return corr[:len(lags)]

def autocorr5(x,lags):
    '''numpy.correlate, non partial'''
    mean=x.mean()
    var=numpy.var(x)
    xp=x-mean
    corr=numpy.correlate(xp,xp,'full')[len(x)-1:]/var/len(x)

    return corr[:len(lags)]


if __name__=='__main__':

    y=[28,28,26,19,16,24,26,24,24,29,29,27,31,26,38,23,13,14,28,19,19,\
            17,22,2,4,5,7,8,14,14,23]
    y=numpy.array(y).astype('float')

    lags=range(15)
    fig,ax=plt.subplots()

    for funcii, labelii in zip([autocorr1, autocorr2, autocorr3, autocorr4,
        autocorr5], ['np.corrcoef, partial', 'manual, non-partial',
            'fft, pad 0s, non-partial', 'fft, no padding, non-partial',
            'np.correlate, non-partial']):

        cii=funcii(y,lags)
        print(labelii)
        print(cii)
        ax.plot(lags,cii,label=labelii)

    ax.set_xlabel('lag')
    ax.set_ylabel('correlation coefficient')
    ax.legend()
    plt.show()

Aqui está o resultado:

insira a descrição da imagem aqui

Não vemos todas as 5 linhas porque 3 delas se sobrepõem (no roxo). As sobreposições são todas autocorrelações não parciais. Isso ocorre porque os cálculos dos métodos de processamento de sinal ( np.correlate, FFT) não calculam uma média / padrão diferente para cada sobreposição.

Observe também que o resultado fft, no padding, non-partial(linha vermelha) é diferente, porque ele não preencheu a série do tempo com 0s antes de fazer FFT, então é FFT circular. Não consigo explicar em detalhes o porquê, foi o que aprendi em outro lugar.

Jason
fonte
12

Como acabei de encontrar o mesmo problema, gostaria de compartilhar algumas linhas de código com você. Na verdade, existem vários posts bastante semelhantes sobre autocorrelação em stackoverflow até agora. Se você definir a autocorrelação como a(x, L) = sum(k=0,N-L-1)((xk-xbar)*(x(k+L)-xbar))/sum(k=0,N-1)((xk-xbar)**2)[esta é a definição dada na função a_correlate do IDL e concorda com o que vejo na resposta 2 da pergunta # 12269834 ], então o seguinte parece dar os resultados corretos:

import numpy as np
import matplotlib.pyplot as plt

# generate some data
x = np.arange(0.,6.12,0.01)
y = np.sin(x)
# y = np.random.uniform(size=300)
yunbiased = y-np.mean(y)
ynorm = np.sum(yunbiased**2)
acor = np.correlate(yunbiased, yunbiased, "same")/ynorm
# use only second half
acor = acor[len(acor)/2:]

plt.plot(acor)
plt.show()

Como você pode ver, testei isso com uma curva de pecado e uma distribuição aleatória uniforme, e ambos os resultados parecem esperados. Observe que eu usei em mode="same"vez de mode="full"como os outros fizeram.

maschu
fonte
9

Sua pergunta 1 já foi amplamente discutida em várias respostas excelentes aqui.

Pensei em compartilhar com vocês algumas linhas de código que permitem calcular a autocorrelação de um sinal com base apenas nas propriedades matemáticas da autocorrelação. Ou seja, a autocorrelação pode ser calculada da seguinte maneira:

  1. subtraia a média do sinal e obtenha um sinal imparcial

  2. calcular a transformada de Fourier do sinal imparcial

  3. calcular a densidade espectral de potência do sinal, tomando a norma quadrada de cada valor da transformada de Fourier do sinal imparcial

  4. calcular a transformada inversa de Fourier da densidade espectral de potência

  5. normalize a transformada inversa de Fourier da densidade espectral de potência pela soma dos quadrados do sinal não enviesado e pegue apenas metade do vetor resultante

O código para fazer isso é o seguinte:

def autocorrelation (x) :
    """
    Compute the autocorrelation of the signal, based on the properties of the
    power spectral density of the signal.
    """
    xp = x-np.mean(x)
    f = np.fft.fft(xp)
    p = np.array([np.real(v)**2+np.imag(v)**2 for v in f])
    pi = np.fft.ifft(p)
    return np.real(pi)[:x.size/2]/np.sum(xp**2)
Ruggero
fonte
é possível que haja algo errado com isso? Não consigo comparar seus resultados com outras funções de correlação automática. A função é semelhante, mas parece um pouco comprimida.
pindakaas
@pindakaas você poderia ser mais específico? forneça informações sobre quais são as discrepâncias que você encontra e com quais funções.
Ruggero
Por que não usar p = np.abs(f)?
dylnan
@dylnan Isso daria os módulos dos componentes de f, enquanto aqui queremos um vetor contendo os módulos quadrados dos componentes de f.
Ruggero
1
Sim, mas você percebeu que fazer a compreensão da lista é provavelmente ainda mais lento.
Jason
2

Sou um biólogo computacional e, quando tive que calcular as correlações auto / cruzadas entre pares de séries temporais de processos estocásticos, percebi que np.correlatenão estava fazendo o trabalho de que precisava.

Na verdade, o que parece estar faltando np.correlateé a média de todos os pares possíveis de pontos de tempo à distância 𝜏.

Aqui está como eu defini uma função fazendo o que eu precisava:

def autocross(x, y):
    c = np.correlate(x, y, "same")
    v = [c[i]/( len(x)-abs( i - (len(x)/2)  ) ) for i in range(len(c))]
    return v

Parece-me que nenhuma das respostas anteriores cobre esta instância de auto / correlação cruzada: espero que esta resposta possa ser útil para alguém que trabalha com processos estocásticos como eu.

Ou então
fonte
1

Eu uso talib.CORREL para autocorrelação como esta, suspeito que você poderia fazer o mesmo com outros pacotes:

def autocorrelate(x, period):

    # x is a deep indicator array 
    # period of sample and slices of comparison

    # oldest data (period of input array) may be nan; remove it
    x = x[-np.count_nonzero(~np.isnan(x)):]
    # subtract mean to normalize indicator
    x -= np.mean(x)
    # isolate the recent sample to be autocorrelated
    sample = x[-period:]
    # create slices of indicator data
    correls = []
    for n in range((len(x)-1), period, -1):
        alpha = period + n
        slices = (x[-alpha:])[:period]
        # compare each slice to the recent sample
        correls.append(ta.CORREL(slices, sample, period)[-1])
    # fill in zeros for sample overlap period of recent correlations    
    for n in range(period,0,-1):
        correls.append(0)
    # oldest data (autocorrelation period) will be nan; remove it
    correls = np.array(correls[-np.count_nonzero(~np.isnan(correls)):])      

    return correls

# CORRELATION OF BEST FIT
# the highest value correlation    
max_value = np.max(correls)
# index of the best correlation
max_index = np.argmax(correls)
litepresença
fonte
1

Usando a transformação de Fourier e o teorema da convolução

A complexidade do tempo é N * log (N)

def autocorr1(x):
    r2=np.fft.ifft(np.abs(np.fft.fft(x))**2).real
    return r2[:len(x)//2]

Aqui está uma versão normalizada e imparcial, também é N * log (N)

def autocorr2(x):
    r2=np.fft.ifft(np.abs(np.fft.fft(x))**2).real
    c=(r2/x.shape-np.mean(x)**2)/np.std(x)**2
    return c[:len(x)//2]

O método fornecido por A. Levy funciona, mas eu testei em meu PC, sua complexidade de tempo parece ser N * N

def autocorr(x):
    result = numpy.correlate(x, x, mode='full')
    return result[result.size/2:]
wwwjjj
fonte
1

Uma alternativa para numpy.correlate está disponível em statsmodels.tsa.stattools.acf () . Isso resulta em uma função de autocorrelação continuamente decrescente como a descrita por OP. Implementar é bastante simples:

from statsmodels.tsa import stattools
# x = 1-D array
# Yield normalized autocorrelation function of number lags
autocorr = stattools.acf( x )

# Get autocorrelation coefficient at lag = 1
autocorr_coeff = autocorr[1]

O comportamento padrão é parar em 40 nlags, mas isso pode ser ajustado com a nlag=opção para seu aplicativo específico. Há uma citação na parte inferior da página para as estatísticas por trás da função .

Fisherp
fonte
0

Acho que a verdadeira resposta para a pergunta do OP está contida de forma sucinta neste trecho da documentação Numpy.correlate:

mode : {'valid', 'same', 'full'}, optional
    Refer to the `convolve` docstring.  Note that the default
    is `valid`, unlike `convolve`, which uses `full`.

Isso implica que, quando usada sem definição de 'modo', a função Numpy.correlate retornará um escalar, quando dado o mesmo vetor para seus dois argumentos de entrada (ou seja, quando usada para realizar autocorrelação).

dbanas
fonte
0

Uma solução simples sem pandas:

import numpy as np

def auto_corrcoef(x):
   return np.corrcoef(x[1:-1], x[2:])[0,1]
dignitas
fonte
0

Trace a autocorrelação estatística dada uma data de tempo do pandas Série de retornos:

import matplotlib.pyplot as plt

def plot_autocorr(returns, lags):
    autocorrelation = []
    for lag in range(lags+1):
        corr_lag = returns.corr(returns.shift(-lag)) 
        autocorrelation.append(corr_lag)
    plt.plot(range(lags+1), autocorrelation, '--o')
    plt.xticks(range(lags+1))
    return np.array(autocorrelation)
Antonio Catalano
fonte
Por que não usar autocorrelation_plot()neste caso? (cf. stats.stackexchange.com/questions/357300/… )
Qaswed