¿Cómo puedo usar numpy.correlate para hacer la autocorrelación?

106

Necesito hacer la autocorrelación de un conjunto de números, que según tengo entendido es solo la correlación del conjunto consigo mismo.

Lo probé usando la función de correlación de numpy, pero no creo en el resultado, ya que casi siempre da un vector donde el primer número no es el más grande, como debería ser.

Entonces, esta pregunta son realmente dos preguntas:

  1. ¿Qué está numpy.correlatehaciendo exactamente ?
  2. ¿Cómo puedo usarlo (o algo más) para hacer la autocorrelación?
Ben
fuente
Consulte también: stackoverflow.com/questions/12269834/… para obtener información sobre la autocorrelación normalizada.
amcnabb

Respuestas:

114

Para responder a su primera pregunta, numpy.correlate(a, v, mode)está realizando la convolución de acon el reverso de vy dando los resultados recortados por el modo especificado. La definición de convolución , C (t) = ∑ -∞ <i <∞ a i v t + i donde -∞ <t <∞, permite resultados de -∞ a ∞, pero obviamente no se puede almacenar un valor infinitamente largo formación. Por lo tanto, debe recortarse, y ahí es donde entra el modo. Hay 3 modos diferentes: completo, igual y válido:

  • El modo "completo" devuelve resultados para todos los tcasos ay ambos vse superponen.
  • El modo "mismo" devuelve un resultado con la misma longitud que el vector más corto ( ao v).
  • "válidos" modo devuelve resultados sólo cuando ay vpor completo se superponen entre sí. La documentación para numpy.convolveda más detalles sobre los modos.

Para tu segunda pregunta, creo que te numpy.correlate está dando la autocorrelación, solo te está dando un poco más también. La autocorrelación se usa para encontrar qué tan similar es una señal o función a sí misma en una determinada diferencia de tiempo. Con una diferencia de tiempo de 0, la autocorrelación debería ser la más alta porque la señal es idéntica a sí misma, por lo que esperaba que el primer elemento de la matriz de resultados de autocorrelación fuera el mayor. Sin embargo, la correlación no comienza con una diferencia de tiempo de 0. Comienza con una diferencia de tiempo negativa, se cierra a 0 y luego se vuelve positiva. Es decir, estabas esperando:

autocorrelación (a) = ∑ -∞ <i <∞ a i v t + i donde 0 <= t <∞

Pero lo que obtuviste fue:

autocorrelación (a) = ∑ -∞ <i <∞ a i v t + i donde -∞ <t <∞

Lo que debe hacer es tomar la última mitad de su resultado de correlación, y esa debería ser la autocorrelación que está buscando. Una función de Python simple para hacer eso sería:

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

Por supuesto, necesitará una verificación de errores para asegurarse de que en xrealidad sea una matriz 1-d. Además, esta explicación probablemente no sea la más rigurosa matemáticamente. He estado lanzando infinitos porque la definición de convolución los usa, pero eso no se aplica necesariamente a la autocorrelación. Por lo tanto, la parte teórica de esta explicación puede ser un poco imprecisa, pero es de esperar que los resultados prácticos sean útiles. Estas páginas sobre autocorrelación son bastante útiles y pueden brindarle una base teórica mucho mejor si no le importa recorrer la notación y los conceptos pesados.

A. Levy
fuente
6
En las versiones actuales de numpy, se puede especificar el modo 'mismo' para lograr exactamente lo que propuso A. Levy. El cuerpo de la función podría leersereturn numpy.correlate(x, x, mode='same')
David Zwicker
13
@DavidZwicker, ¡pero los resultados son diferentes! np.correlate(x,x,mode='full')[len(x)//2:] != np.correlate(x,x,mode='same'). Por ejemplo, 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])}. La correcta es: np.correlate(x,x,mode='full')[len(x)-1:];{ >>> array([19, 13, 11, 5, 2])} vea que el primer elemento es el más grande .
Desarrollador
19
Tenga en cuenta que esta respuesta da la autocorrelación no normalizada.
amcnabb
4
Creo que @Developer da el corte correcto: [len(x)-1:]comienza desde el retraso 0. Debido a que el fullmodo da un tamaño de resultado 2*len(x)-1, el de A.Levy [result.size/2:]es lo mismo que [len(x)-1:]. Sin embargo, es mejor convertirlo en un int, como [result.size//2:].
Jason
Descubrí que debe ser un int, al menos en Python 3.7
kevinkayaks
25

La autocorrelación viene en dos versiones: estadística y convolución. Ambos hacen lo mismo, excepto por un pequeño detalle: la versión estadística está normalizada para estar en el intervalo [-1,1]. Aquí hay un ejemplo de cómo se hace la estadística:

def acf(x, length=20):
    return numpy.array([1]+[numpy.corrcoef(x[:-i], x[i:])[0,1]  \
        for i in range(1, length)])
Jonathf
fuente
9
Quiere numpy.corrcoef[x:-i], x[i:])[0,1]en la segunda línea ya que el valor de retorno de corrcoefes una matriz de 2x2
luispedro
¿Cuál es la diferencia entre las autocorrelaciones estadísticas y convolucionales?
Daniel dice Reincorporar a Monica
1
@DanielPendergast: La segunda oración responde que: Ambos hacen lo mismo, excepto por un pequeño detalle: el primero [estadístico] está normalizado para estar en el intervalo [-1,1]
n1k31t4
21

Utilice la numpy.corrcoeffunción en lugar de numpy.correlatepara calcular la correlación estadística para un retraso de t:

def autocorr(x, t=1):
    return numpy.corrcoef(numpy.array([x[:-t], x[t:]]))
Ramón J Romero y Vigil
fuente
¿No se refieren los "coeficientes de correlación" a la autocorrelación utilizada en el procesamiento de señales y no a la autocorrelación utilizada en las estadísticas? en.wikipedia.org/wiki/Autocorrelation#Signal_processing
Daniel dice Reincorporar a Monica
@DanielPendergast No estoy tan familiarizado con el procesamiento de señales. De los documentos numpy: "Devolver coeficientes de correlación producto-momento de Pearson". ¿Es esa la versión de procesamiento de señales?
Ramón J Romero y Vigil
18

Creo que hay 2 cosas que agregan confusión a este tema:

  1. definición de procesamiento de señal vs estadística: como otros han señalado, en estadística normalizamos la autocorrelación en [-1,1].
  2. media / varianza parcial versus no parcial: cuando la serie temporal cambia con un retraso> 0, su tamaño de superposición siempre será <longitud original. ¿Usamos la media y std del original (no parcial), o siempre calculamos una nueva media y std usando la superposición en constante cambio (parcial) que marca la diferencia? (Probablemente haya un término formal para esto, pero por ahora usaré "parcial").

He creado 5 funciones que calculan la autocorrelación de una matriz 1d, con distinciones parciales y no parciales. Algunos usan fórmulas de estadísticas, algunos usan correlación en el sentido de procesamiento de señales, lo que también se puede hacer mediante FFT. Pero todos los resultados son autocorrelaciones en la definición de estadísticas , por lo que ilustran cómo están vinculados entre sí. Código a continuación:

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()

Aquí está la figura de salida:

ingrese la descripción de la imagen aquí

No vemos las 5 líneas porque 3 de ellas se superponen (en la púrpura). Las superposiciones son todas autocorrelaciones no parciales. Esto se debe a que los cálculos de los métodos de procesamiento de señales ( np.correlate, FFT) no calculan una media / estándar diferente para cada superposición.

También tenga en cuenta que el resultado fft, no padding, non-partial(línea roja) es diferente, porque no rellenó la serie temporal con 0 antes de hacer FFT, por lo que es circular FFT. No puedo explicar en detalle por qué, eso es lo que aprendí en otros lugares.

Jason
fuente
12

Como acabo de encontrarme con el mismo problema, me gustaría compartir algunas líneas de código con ustedes. De hecho, hay varias publicaciones bastante similares sobre la autocorrelación en stackoverflow a estas alturas. Si define la autocorrelación 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 es la definición dada en la función a_correlate de IDL y está de acuerdo con lo que veo en la respuesta 2 de la pregunta # 12269834 ], entonces lo siguiente parece dar los resultados correctos:

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 puede ver, he probado esto con una curva sin y una distribución aleatoria uniforme, y ambos resultados se ven como los esperaría. Tenga en cuenta que usé en mode="same"lugar de lo mode="full"que hicieron los demás.

maschu
fuente
9

Su pregunta 1 ya se ha discutido ampliamente en varias respuestas excelentes aquí.

Pensé en compartir con ustedes algunas líneas de código que les permiten calcular la autocorrelación de una señal basándose únicamente en las propiedades matemáticas de la autocorrelación. Es decir, la autocorrelación se puede calcular de la siguiente manera:

  1. restar la media de la señal y obtener una señal insesgada

  2. calcular la transformada de Fourier de la señal insesgada

  3. Calcule la densidad espectral de potencia de la señal, tomando la norma cuadrada de cada valor de la transformada de Fourier de la señal insesgada.

  4. calcular la transformada de Fourier inversa de la densidad espectral de potencia

  5. normalizar la transformada de Fourier inversa de la densidad espectral de potencia mediante la suma de los cuadrados de la señal insesgada y tomar solo la mitad del vector resultante

El código para hacer esto es el siguiente:

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
fuente
¿Es posible que haya algo mal en esto? No puedo comparar sus resultados con otras funciones de autocorrelación. La función se ve similar pero parece algo aplastada.
pindakaas
@pindakaas, ¿podrías ser más específico? proporcione información sobre las discrepancias que encuentre, con qué funciones.
Ruggero
¿Por qué no usar p = np.abs(f)?
dylnan
@dylnan Eso daría los módulos de las componentes de f, mientras que aquí queremos un vector que contenga los módulos cuadrados de las componentes de f.
Ruggero
1
Sí, pero ¿te diste cuenta de que la comprensión de listas es probablemente incluso más lenta?
Jason
2

Soy biólogo computacional, y cuando tuve que calcular las correlaciones automáticas / cruzadas entre pares de series de tiempo de procesos estocásticos, me di cuenta de que np.correlateno estaba haciendo el trabajo que necesitaba.

De hecho, lo que parece faltar np.correlatees el promedio de todas las parejas posibles de puntos de tiempo a la distancia 𝜏.

Así es como definí una función haciendo lo que necesitaba:

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

Me parece que ninguna de las respuestas anteriores cubre esta instancia de correlación automática / cruzada: espero que esta respuesta pueda ser útil para alguien que trabaja en procesos estocásticos como yo.

Más o menos
fuente
1

Yo uso talib.CORREL para autocorrelación como esta, sospecho que podrías hacer lo mismo con otros paquetes:

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)
presencia ligera
fuente
1

Usando la transformación de Fourier y el teorema de convolución

La complejidad del tiempo es N * log (N)

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

Aquí hay una versión normalizada e imparcial, también es 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]

El método proporcionado por A. Levy funciona, pero lo probé en mi PC, su complejidad de tiempo parece ser N * N

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

Una alternativa a numpy.correlate está disponible en statsmodels.tsa.stattools.acf () . Esto produce una función de autocorrelación continuamente decreciente como la descrita por OP. Implementarlo es bastante simple:

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]

El comportamiento predeterminado es detenerse en 40 nlags, pero esto se puede ajustar con la nlag=opción para su aplicación específica. Hay una cita en la parte inferior de la página para las estadísticas detrás de la función .

pescador
fuente
0

Creo que la respuesta real a la pregunta del OP está contenida sucintamente en este extracto de la documentación de Numpy.correlate:

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

Esto implica que, cuando se usa sin una definición de 'modo', la función Numpy.correlate devolverá un escalar, cuando se le proporcione el mismo vector para sus dos argumentos de entrada (es decir, cuando se use para realizar la autocorrelación).

dbanas
fuente
0

Una solución simple sin pandas:

import numpy as np

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

Grafique la autocorrelación estadística dada una serie de rendimientos de pandas datatime:

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
fuente
¿Por qué no usarlo autocorrelation_plot()en este caso? (cf. stats.stackexchange.com/questions/357300/… )
Qaswed