Розрахунок кореляції та значення Пірсона в Python


Відповіді:


202

Ви можете подивитися 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
Як щодо коефіцієнта кореляції двох словників ?!
користувач702846

2
@ user702846 Кореляція Пірсона визначається на матриці 2xN. Не існує загальноприйнятого методу, який перетворює два словники в матрицю 2xN, але ви можете використовувати масив пар значень словника, що відповідає клавішам перетину ключів ваших словників.
winerd


56

Альтернативою може бути нативна наукова функція від зворотного переходу, яка обчислює:

нахил: нахил лінії регресії

перехоплення: перехоплення лінії регресії

r-значення: коефіцієнт кореляції

p-значення: двостороннє p-значення для тесту гіпотези, нульовою гіпотезою якого є те, що нахил дорівнює нулю

stderr: Стандартна помилка оцінки

І ось приклад:

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)

поверне вам:

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

2
Чудова відповідь - на сьогоднішній день найбільш інформативна. Також працює з дворядними пандами.DataFrame:lineregress(two_row_df)
dmeu

Блискуча відповідь. Дуже інтуїтивний теж, якщо ви про це думаєте
Raghuram

37

Якщо ви не хочете встановлювати scipy, я скористався цим швидким злому, трохи зміненим з програмування колективного інтелекту :

(Відредаговано для коректності.)

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
Я був здивований, виявивши, що це не погоджується з Excel, NumPy та R. Дивіться stackoverflow.com/questions/3949226/… .
dfrankow

2
Як зауважив інший коментатор, це помилка float / int. Я думаю, що sum_y / n - ціле ділення для ints. Якщо ви використовуєте sum_x = float (sum (x)) та sum_y = float (sum (y)), він працює.
dfrankow

@dfrankow Я думаю, що це тому, що Imap не може впоратися з плаванням. пітон дає оцінку TypeError: unsupported operand type(s) for -: 'itertools.imap' and 'float'наnum = psum - (sum_x * sum_y/n)
Alvas

4
Як примітка стилю, Python хмуриться на це непотрібне використання карти (на користь списків)
Максим Хесін

14
Як коментар, врахуйте, що бібліотеки як scipy та ін розроблені людьми, які знають численний чисельний аналіз. Це може уникнути багатьох поширених підводних каменів (наприклад, дуже великі та дуже малі цифри у X чи Y можуть призвести до катастрофічного скасування)
geekazoid

32

Наступний код - це прямолінійне тлумачення визначення :

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)

Тест:

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

повертає

0.981980506062

З цим погоджується Excel, цей калькулятор , SciPy (також NumPy ), який повертає 0,981980506 та 0,9819805060619657 та 0,98198050606196574 відповідно.

R :

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

EDIT : виправлено помилку, на яку вказав коментатор.


4
Остерігайтеся типу змінних! Ви зіткнулися з проблемою int / float. У sum(x) / len(x)вас ділиться int, а не floats. Отже sum([1,5,7]) / len([1,5,7]) = 13 / 3 = 4, за цілим поділом (тоді як ви хочете 13. / 3. = 4.33...). Щоб виправити це, перепишіть цей рядок як float(sum(x)) / float(len(x))(достатньо одного поплавця, оскільки Python перетворює його автоматично).
Piotr Migdal

Ваш код не працюватиме у випадках: [10,10,10], [0,0,0] або [10,10], [10,0]. або навіть [10,10], [10,10]
madCode

4
Коефіцієнт кореляції не визначений для жодного з цих випадків. Поміщення їх у R повертає "NA" для всіх трьох.
dfrankow

28

Ви також можете це зробити 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()

Це дає

          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
Це лише кореляція без значущості
Івелін

12

Замість того, щоб покладатися на numpy / scipy, я думаю, що моя відповідь повинна бути найпростішою для кодування та розуміння кроків підрахунку коефіцієнта кореляції Пірсона (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)

Значення ОКК в основному , щоб показати вам , як сильно корелюють дві змінні / списки. Важливо зазначити, що значення PCC коливається від -1 до 1 . Значення від 0 до 1 позначає позитивну кореляцію. Значення 0 = найвища варіація (ніякої кореляції немає). Значення від -1 до 0 позначає негативну кореляцію.


2
Зауважте, що Python має вбудовану sumфункцію.
bfontaine

5
Він має дивовижну складність та повільну продуктивність у двох списках із значеннями 500+.
Микола Фоміних

9

Розрахунок коефіцієнта Пірсона за допомогою панд у python: Я б запропонував спробувати цей підхід, оскільки ваші дані містять списки. Взаємодіяти з вашими даними буде легко та керувати ними з консолі, оскільки ви зможете візуалізувати структуру даних та оновлювати її за своїм бажанням. Ви також можете експортувати набір даних і зберегти їх та додати нові дані з консолі python для подальшого аналізу. Цей код простіший і містить менше рядків коду. Я припускаю, що вам потрібно кілька швидких рядків коду для екранізації ваших даних для подальшого аналізу

Приклад:

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 

Однак ви не опублікували свої дані для мене, щоб побачити розмір набору даних або перетворення, які можуть знадобитися перед аналізом.


Привіт, Ласкаво просимо до StackOverflow! Спробуйте додати короткий опис того, чому ви вибрали цей код і як він застосовується в цьому випадку на початку вашої відповіді!
Трісто

8

Хм, багато з цих відповідей давно і важко читати код ...

Я б запропонував використовувати numpy з його чудовими функціями при роботі з масивами:

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)

Хоча мені ця відповідь дуже подобається, я б радив скопіювати / клонувати як X, так і Y всередині функції. Інакше обидва змінені, що може бути не бажаною поведінкою.
antonimmo

7

Це реалізація функції кореляції Пірсона за допомогою 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

Ось варіант відповіді mkh, який працює набагато швидше за нього, і scipy.stats.pearsonr, використовуючи 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

Ось реалізація кореляції груші на основі розрідженого вектора. Тут вектори виражаються у вигляді списку кортежів, виражених як (індекс, значення). Два розріджені вектори можуть бути різної довжини, але для всіх розмірів вектора повинні бути однакові. Це корисно для додатків для видобутку тексту, де розмір вектора надзвичайно великий, оскільки більшість функцій є пакетом слів, а значить, обчислення зазвичай проводяться за допомогою рідких векторів.

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)

Тестові одиниці:

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

У мене дуже просте і зрозуміле для цього рішення. Для двох масивів однакової довжини коефіцієнт Пірсона можна легко обчислити так:

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

Вам може бути цікаво, як інтерпретувати свою ймовірність у контексті пошуку кореляції у певному напрямку (негативна чи позитивна кореляція.) Ось функція, яку я написав, щоб допомогти у цьому. Це може бути навіть правильно!

Він заснований на інформації, яку я отримав з http://www.vassarstats.net/rsig.html та http://en.wikipedia.org/wiki/Student%27s_t_distribution , завдяки іншим відповідям, розміщеним тут.

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

1

Ви можете поглянути на цю статтю. Це добре задокументований приклад для обчислення кореляції на основі даних історичних валютних пар валют з декількох файлів за допомогою бібліотеки панд (для Python), а потім генерування сюжетного тепла за допомогою бібліотеки новонароджених.

http://www.tradinggeeks.net/2015/08/calculating-correlation-in-python/


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

Відповіді, що стосуються лише коду, не вважаються хорошою практикою. Будь ласка, додайте кілька слів, щоб пояснити, як ваш код вирішує питання. (читайте сторінку довідки про те, як відповісти на запитання щодо SO)
Янніс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.