Calculando a correlação e a significância de Pearson em Python


Respostas:


202

Você pode dar uma olhada em scipy.stats:

from pydoc import help
from scipy.stats.stats import pearsonr
help(pearsonr)

>>>
Help on function pearsonr in module scipy.stats.stats:

pearsonr(x, y)
 Calculates a Pearson correlation coefficient and the p-value for testing
 non-correlation.

 The Pearson correlation coefficient measures the linear relationship
 between two datasets. Strictly speaking, Pearson's correlation requires
 that each dataset be normally distributed. Like other correlation
 coefficients, this one varies between -1 and +1 with 0 implying no
 correlation. Correlations of -1 or +1 imply an exact linear
 relationship. Positive correlations imply that as x increases, so does
 y. Negative correlations imply that as x increases, y decreases.

 The p-value roughly indicates the probability of an uncorrelated system
 producing datasets that have a Pearson correlation at least as extreme
 as the one computed from these datasets. The p-values are not entirely
 reliable but are probably reasonable for datasets larger than 500 or so.

 Parameters
 ----------
 x : 1D array
 y : 1D array the same length as x

 Returns
 -------
 (Pearson's correlation coefficient,
  2-tailed p-value)

 References
 ----------
 http://www.statsoft.com/textbook/glosp.html#Pearson%20Correlation

2
Que tal o coeficiente de correlação de dois dicionários ?!
User702846 22/05

2
@ user702846 A correlação de Pearson é definida em uma matriz 2xN. Geralmente, não existe um método aplicável que converta dois dicionários em uma matriz 2xN, mas você pode usar a matriz de pares de valores de dicionário correspondentes às chaves da interseção das chaves dos seus dicionários.
winerd


56

Uma alternativa pode ser uma função scipy nativa do linregress que calcula:

inclinação: inclinação da linha de regressão

interceptação: interceptação da linha de regressão

Valor r: coeficiente de correlação

Valor p: valor p frente e verso para um teste de hipótese cuja hipótese nula é a de que a inclinação é zero

stderr: erro padrão da estimativa

E aqui está um exemplo:

a = [15, 12, 8, 8, 7, 7, 7, 6, 5, 3]
b = [10, 25, 17, 11, 13, 17, 20, 13, 9, 15]
from scipy.stats import linregress
linregress(a, b)

retornará você:

LinregressResult(slope=0.20833333333333337, intercept=13.375, rvalue=0.14499815458068521, pvalue=0.68940144811669501, stderr=0.50261704627083648)

2
Ótima resposta - de longe a mais informativa. Também trabalha com uma pandas.DataFrame de duas linhas:lineregress(two_row_df)
DMEU

Resposta brilhante. Muito intuitivo também, se você pensar sobre isso
Raghuram

37

Se você não deseja instalar o scipy, usei esse hack rápido, ligeiramente modificado em Programming Collective Intelligence :

(Editado para correção.)

from itertools import imap

def pearsonr(x, y):
  # Assume len(x) == len(y)
  n = len(x)
  sum_x = float(sum(x))
  sum_y = float(sum(y))
  sum_x_sq = sum(map(lambda x: pow(x, 2), x))
  sum_y_sq = sum(map(lambda x: pow(x, 2), y))
  psum = sum(imap(lambda x, y: x * y, x, y))
  num = psum - (sum_x * sum_y/n)
  den = pow((sum_x_sq - pow(sum_x, 2) / n) * (sum_y_sq - pow(sum_y, 2) / n), 0.5)
  if den == 0: return 0
  return num / den

2
Fiquei surpreso ao descobrir que isso não concorda com o Excel, NumPy e R. Consulte stackoverflow.com/questions/3949226/… .
dfrankow

2
Como outro comentarista apontou, isso tem um bug float / int. Eu acho que sum_y / n é a divisão inteira para ints. Se você usar sum_x = float (soma (x)) e sum_y = float (soma (y)), ele funcionará.
dfrankow

@dfrankow Eu acho que é porque o imap não pode lidar com float. python dá um TypeError: unsupported operand type(s) for -: 'itertools.imap' and 'float'atnum = psum - (sum_x * sum_y/n)
alvas

4
Como um carrancas estilo da nota de Python sobre esse uso desnecessário de mapa (em favor de compreensões lista)
Maxim Khesin

14
Apenas como comentário, considere que as bibliotecas como scipy et al são desenvolvidas por pessoas que conhecem muita análise numérica. Isso pode evitar um monte de armadilhas comuns (por exemplo, com números muito grandes ou muito pequenos em X ou Y pode resultar no cancelamento catastrofic)
geekazoid

32

O código a seguir é uma interpretação direta da definição :

import math

def average(x):
    assert len(x) > 0
    return float(sum(x)) / len(x)

def pearson_def(x, y):
    assert len(x) == len(y)
    n = len(x)
    assert n > 0
    avg_x = average(x)
    avg_y = average(y)
    diffprod = 0
    xdiff2 = 0
    ydiff2 = 0
    for idx in range(n):
        xdiff = x[idx] - avg_x
        ydiff = y[idx] - avg_y
        diffprod += xdiff * ydiff
        xdiff2 += xdiff * xdiff
        ydiff2 += ydiff * ydiff

    return diffprod / math.sqrt(xdiff2 * ydiff2)

Teste:

print pearson_def([1,2,3], [1,5,7])

retorna

0.981980506062

Isso concorda com o Excel, esta calculadora , SciPy (também NumPy ), que retorna 0,981980506 e 0,9819805060619657 e 0,98198050606196574, respectivamente.

R :

> cor( c(1,2,3), c(1,5,7))
[1] 0.9819805

EDIT : Corrigido um bug apontado por um comentarista.


4
Cuidado com o tipo de variáveis! Você encontrou um problema int / float. Em sum(x) / len(x)você divide ints, não flutua. Então sum([1,5,7]) / len([1,5,7]) = 13 / 3 = 4, de acordo com a divisão inteira (enquanto você deseja 13. / 3. = 4.33...). Para corrigi-lo, reescreva esta linha como float(sum(x)) / float(len(x))(um float é suficiente, pois o Python a converte automaticamente).
Piotr Migdal 30/10

Seu código não funcionará em casos como: [10,10,10], [0,0,0] ou [10,10], [10,0]. ou até mesmo [10,10], [10,10]
madCode

4
O coeficiente de correlação não está definido para nenhum desses casos. Colocá-los em R retorna "NA" para todos os três.
Dfrankow 18/05/12

28

Você também pode fazer isso com pandas.DataFrame.corr:

import pandas as pd
a = [[1, 2, 3],
     [5, 6, 9],
     [5, 6, 11],
     [5, 6, 13],
     [5, 3, 13]]
df = pd.DataFrame(data=a)
df.corr()

Isto dá

          0         1         2
0  1.000000  0.745601  0.916579
1  0.745601  1.000000  0.544248
2  0.916579  0.544248  1.000000

5
Este é apenas correlação sem significância
Ivelin

12

Em vez de depender de numpy / scipy, acho que minha resposta deve ser a mais fácil de codificar e entender as etapas do cálculo do coeficiente de correlação de Pearson (PCC).

import math

# calculates the mean
def mean(x):
    sum = 0.0
    for i in x:
         sum += i
    return sum / len(x) 

# calculates the sample standard deviation
def sampleStandardDeviation(x):
    sumv = 0.0
    for i in x:
         sumv += (i - mean(x))**2
    return math.sqrt(sumv/(len(x)-1))

# calculates the PCC using both the 2 functions above
def pearson(x,y):
    scorex = []
    scorey = []

    for i in x: 
        scorex.append((i - mean(x))/sampleStandardDeviation(x)) 

    for j in y:
        scorey.append((j - mean(y))/sampleStandardDeviation(y))

# multiplies both lists together into 1 list (hence zip) and sums the whole list   
    return (sum([i*j for i,j in zip(scorex,scorey)]))/(len(x)-1)

O significado do PCC é basicamente mostrar o quão fortemente correlacionadas as duas variáveis ​​/ listas estão. É importante observar que o valor do PCC varia de -1 a 1 . Um valor entre 0 e 1 indica uma correlação positiva. Valor de 0 = variação mais alta (sem correlação). Um valor entre -1 e 0 indica uma correlação negativa.


2
Observe que o Python tem uma sumfunção interna .
bfontaine

5
Tem uma complexidade incrível e desempenho lento em 2 listas com mais de 500 valores.
Nikolay Fominyh

9

Cálculo do coeficiente de Pearson usando pandas em python: sugiro tentar essa abordagem, pois seus dados contêm listas. Será fácil interagir com seus dados e manipulá-los no console, pois você pode visualizar sua estrutura de dados e atualizá-la como desejar. Você também pode exportar o conjunto de dados, salvá-lo e adicionar novos dados a partir do console python para análise posterior. Este código é mais simples e contém menos linhas de código. Suponho que você precise de algumas linhas rápidas de código para rastrear seus dados para análise posterior

Exemplo:

data = {'list 1':[2,4,6,8],'list 2':[4,16,36,64]}

import pandas as pd #To Convert your lists to pandas data frames convert your lists into pandas dataframes

df = pd.DataFrame(data, columns = ['list 1','list 2'])

from scipy import stats # For in-built method to get PCC

pearson_coef, p_value = stats.pearsonr(df["list 1"], df["list 2"]) #define the columns to perform calculations on
print("Pearson Correlation Coefficient: ", pearson_coef, "and a P-value of:", p_value) # Results 

No entanto, você não publicou seus dados para ver o tamanho do conjunto de dados ou as transformações que podem ser necessárias antes da análise.


Olá, bem-vindo ao StackOverflow! Tente adicionar uma breve descrição do motivo pelo qual você escolheu esse código e como ele se aplica nesse caso no início de sua resposta!
Tristo 9/09/18

8

Hmm, muitas dessas respostas têm código longo e difícil de ler ...

Eu sugiro usar numpy com seus recursos bacanas ao trabalhar com matrizes:

import numpy as np
def pcc(X, Y):
   ''' Compute Pearson Correlation Coefficient. '''
   # Normalise X and Y
   X -= X.mean(0)
   Y -= Y.mean(0)
   # Standardise X and Y
   X /= X.std(0)
   Y /= Y.std(0)
   # Compute mean product
   return np.mean(X*Y)

# Using it on a random example
from random import random
X = np.array([random() for x in xrange(100)])
Y = np.array([random() for x in xrange(100)])
pcc(X, Y)

Embora eu goste muito dessa resposta, aconselho a copiar / clonar X e Y dentro da função. Caso contrário, ambos são alterados, o que pode não ser o comportamento desejado.
antonimmo

7

Esta é uma implementação da função Correlação de Pearson usando numpy:


def corr(data1, data2):
    "data1 & data2 should be numpy arrays."
    mean1 = data1.mean() 
    mean2 = data2.mean()
    std1 = data1.std()
    std2 = data2.std()

#     corr = ((data1-mean1)*(data2-mean2)).mean()/(std1*std2)
    corr = ((data1*data2).mean()-mean1*mean2)/(std1*std2)
    return corr


7

Aqui está uma variante da resposta do mkh que corre muito mais rápido que ela e scipy.stats.pearsonr, usando o numba.

import numba

@numba.jit
def corr(data1, data2):
    M = data1.size

    sum1 = 0.
    sum2 = 0.
    for i in range(M):
        sum1 += data1[i]
        sum2 += data2[i]
    mean1 = sum1 / M
    mean2 = sum2 / M

    var_sum1 = 0.
    var_sum2 = 0.
    cross_sum = 0.
    for i in range(M):
        var_sum1 += (data1[i] - mean1) ** 2
        var_sum2 += (data2[i] - mean2) ** 2
        cross_sum += (data1[i] * data2[i])

    std1 = (var_sum1 / M) ** .5
    std2 = (var_sum2 / M) ** .5
    cross_mean = cross_sum / M

    return (cross_mean - mean1 * mean2) / (std1 * std2)

5

Aqui está uma implementação para a correlação de pearson com base no vetor esparso. Os vetores aqui são expressos como uma lista de tuplas expressas como (índice, valor). Os dois vetores esparsos podem ter comprimentos diferentes, mas o tamanho de todo o vetor deverá ser o mesmo. Isso é útil para aplicativos de mineração de texto em que o tamanho do vetor é extremamente grande devido ao fato de a maioria dos recursos serem um conjunto de palavras e, portanto, os cálculos geralmente são realizados usando vetores esparsos.

def get_pearson_corelation(self, first_feature_vector=[], second_feature_vector=[], length_of_featureset=0):
    indexed_feature_dict = {}
    if first_feature_vector == [] or second_feature_vector == [] or length_of_featureset == 0:
        raise ValueError("Empty feature vectors or zero length of featureset in get_pearson_corelation")

    sum_a = sum(value for index, value in first_feature_vector)
    sum_b = sum(value for index, value in second_feature_vector)

    avg_a = float(sum_a) / length_of_featureset
    avg_b = float(sum_b) / length_of_featureset

    mean_sq_error_a = sqrt((sum((value - avg_a) ** 2 for index, value in first_feature_vector)) + ((
        length_of_featureset - len(first_feature_vector)) * ((0 - avg_a) ** 2)))
    mean_sq_error_b = sqrt((sum((value - avg_b) ** 2 for index, value in second_feature_vector)) + ((
        length_of_featureset - len(second_feature_vector)) * ((0 - avg_b) ** 2)))

    covariance_a_b = 0

    #calculate covariance for the sparse vectors
    for tuple in first_feature_vector:
        if len(tuple) != 2:
            raise ValueError("Invalid feature frequency tuple in featureVector: %s") % (tuple,)
        indexed_feature_dict[tuple[0]] = tuple[1]
    count_of_features = 0
    for tuple in second_feature_vector:
        count_of_features += 1
        if len(tuple) != 2:
            raise ValueError("Invalid feature frequency tuple in featureVector: %s") % (tuple,)
        if tuple[0] in indexed_feature_dict:
            covariance_a_b += ((indexed_feature_dict[tuple[0]] - avg_a) * (tuple[1] - avg_b))
            del (indexed_feature_dict[tuple[0]])
        else:
            covariance_a_b += (0 - avg_a) * (tuple[1] - avg_b)

    for index in indexed_feature_dict:
        count_of_features += 1
        covariance_a_b += (indexed_feature_dict[index] - avg_a) * (0 - avg_b)

    #adjust covariance with rest of vector with 0 value
    covariance_a_b += (length_of_featureset - count_of_features) * -avg_a * -avg_b

    if mean_sq_error_a == 0 or mean_sq_error_b == 0:
        return -1
    else:
        return float(covariance_a_b) / (mean_sq_error_a * mean_sq_error_b)

Testes unitários:

def test_get_get_pearson_corelation(self):
    vector_a = [(1, 1), (2, 2), (3, 3)]
    vector_b = [(1, 1), (2, 5), (3, 7)]
    self.assertAlmostEquals(self.sim_calculator.get_pearson_corelation(vector_a, vector_b, 3), 0.981980506062, 3, None, None)

    vector_a = [(1, 1), (2, 2), (3, 3)]
    vector_b = [(1, 1), (2, 5), (3, 7), (4, 14)]
    self.assertAlmostEquals(self.sim_calculator.get_pearson_corelation(vector_a, vector_b, 5), -0.0137089240555, 3, None, None)

3

Eu tenho uma solução muito simples e fácil de entender para isso. Para duas matrizes de comprimento igual, o coeficiente de Pearson pode ser facilmente calculado da seguinte maneira:

def manual_pearson(a,b):
"""
Accepts two arrays of equal length, and computes correlation coefficient. 
Numerator is the sum of product of (a - a_avg) and (b - b_avg), 
while denominator is the product of a_std and b_std multiplied by 
length of array. 
"""
  a_avg, b_avg = np.average(a), np.average(b)
  a_stdev, b_stdev = np.std(a), np.std(b)
  n = len(a)
  denominator = a_stdev * b_stdev * n
  numerator = np.sum(np.multiply(a-a_avg, b-b_avg))
  p_coef = numerator/denominator
  return p_coef

1

Você pode se perguntar como interpretar sua probabilidade no contexto de procurar uma correlação em uma direção específica (correlação negativa ou positiva). Aqui está uma função que escrevi para ajudar nisso. Pode até estar certo!

É baseado nas informações que eu colhi de http://www.vassarstats.net/rsig.html e http://en.wikipedia.org/wiki/Student%27s_t_distribution , graças a outras respostas postadas aqui.

# Given (possibly random) variables, X and Y, and a correlation direction,
# returns:
#  (r, p),
# where r is the Pearson correlation coefficient, and p is the probability
# that there is no correlation in the given direction.
#
# direction:
#  if positive, p is the probability that there is no positive correlation in
#    the population sampled by X and Y
#  if negative, p is the probability that there is no negative correlation
#  if 0, p is the probability that there is no correlation in either direction
def probabilityNotCorrelated(X, Y, direction=0):
    x = len(X)
    if x != len(Y):
        raise ValueError("variables not same len: " + str(x) + ", and " + \
                         str(len(Y)))
    if x < 6:
        raise ValueError("must have at least 6 samples, but have " + str(x))
    (corr, prb_2_tail) = stats.pearsonr(X, Y)

    if not direction:
        return (corr, prb_2_tail)

    prb_1_tail = prb_2_tail / 2
    if corr * direction > 0:
        return (corr, prb_1_tail)

    return (corr, 1 - prb_1_tail)


0
def pearson(x,y):
  n=len(x)
  vals=range(n)

  sumx=sum([float(x[i]) for i in vals])
  sumy=sum([float(y[i]) for i in vals])

  sumxSq=sum([x[i]**2.0 for i in vals])
  sumySq=sum([y[i]**2.0 for i in vals])

  pSum=sum([x[i]*y[i] for i in vals])
  # Calculating Pearson correlation
  num=pSum-(sumx*sumy/n)
  den=((sumxSq-pow(sumx,2)/n)*(sumySq-pow(sumy,2)/n))**.5
  if den==0: return 0
  r=num/den
  return r

Respostas somente de código não são consideradas boas práticas. Considere adicionar algumas palavras para explicar como o seu código aborda a questão. (leia a página de ajuda sobre como responder a uma pergunta sobre SO)
Yannis
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.