如何使用Python和Numpy计算r平方?


89

我正在使用Python和Numpy计算任意次数的最佳拟合多项式。我传递了x值,y值以及要拟合的多项式的阶数(线性,二次等)的列表。

这很有效,但是我还想计算r(相关系数)和r-平方(确定系数)。我正在将结果与Excel的最佳拟合趋势线功能及其计算的r平方值进行比较。使用此方法,我知道我正在为线性最佳拟合(度等于1)正确计算r平方。但是,我的函数不适用于度数大于1的多项式。

Excel能够做到这一点。如何使用Numpy计算高阶多项式的r平方?

这是我的功能:

import numpy

# Polynomial Regression
def polyfit(x, y, degree):
    results = {}

    coeffs = numpy.polyfit(x, y, degree)
     # Polynomial Coefficients
    results['polynomial'] = coeffs.tolist()

    correlation = numpy.corrcoef(x, y)[0,1]

     # r
    results['correlation'] = correlation
     # r-squared
    results['determination'] = correlation**2

    return results

1
注意:仅在计算系数时使用度数。
尼克·丹杜拉基斯

tydok是正确的。您正在计算y = p_0 + p_1 * x的x和y和r平方的相关性。请参阅下面的答案,了解一些应该起作用的代码。如果您不介意我问,您的最终目标是什么?您是否正在选择模型(选择使用何种程度)?或者是其他东西?
leif

@leif-请求归结为“像Excel一样执行”。从这些答案中我得到的感觉是,使用非线性最佳拟合曲线时,用户可能对r平方值的读数过多。但是,我不是数学向导,这是所要求的功能。
特拉维斯·比尔

Answers:


60

numpy.polyfit文档中,它适合线性回归。具体来说,度为'd'的numpy.polyfit与均值函数拟合线性回归

E(y | x)= p_d * x ** d + p_ {d-1} * x **(d-1)+ ... + p_1 * x + p_0

因此,您只需要计算该拟合的R平方即可。维基百科页面线性回归提供了完整的详细信息。您对R ^ 2感兴趣,可以用几种方法来计算,最容易的可能是

SST = Sum(i=1..n) (y_i - y_bar)^2
SSReg = Sum(i=1..n) (y_ihat - y_bar)^2
Rsquared = SSReg/SST

我将“ y_bar”用作y的均值,并将“ y_ihat”作为每个点的拟合值。

我对numpy不太熟悉(我通常在R中工作),因此可能有一种更简洁的方法来计算R平方,但是以下应该是正确的

import numpy

# Polynomial Regression
def polyfit(x, y, degree):
    results = {}

    coeffs = numpy.polyfit(x, y, degree)

     # Polynomial Coefficients
    results['polynomial'] = coeffs.tolist()

    # r-squared
    p = numpy.poly1d(coeffs)
    # fit values, and mean
    yhat = p(x)                         # or [p(z) for z in x]
    ybar = numpy.sum(y)/len(y)          # or sum(y)/len(y)
    ssreg = numpy.sum((yhat-ybar)**2)   # or sum([ (yihat - ybar)**2 for yihat in yhat])
    sstot = numpy.sum((y - ybar)**2)    # or sum([ (yi - ybar)**2 for yi in y])
    results['determination'] = ssreg / sstot

    return results

5
我只想指出,使用numpy数组函数代替列表解析会更快,例如numpy.sum((yi-ybar)** 2)且更易于阅读
Josef 2010年

17
根据wiki页面en.wikipedia.org/wiki/Coefficient_of_determination的定义,R ^ 2的最一般定义是R^2 = 1 - SS_err/SS_totR^2 = SS_reg/SS_tot只是一个特例。
LWZ

134

回复很晚,但以防万一有人需要为此准备就绪的功能:

scipy.stats.linregress

slope, intercept, r_value, p_value, std_err = scipy.stats.linregress(x, y)

就像@Adam Marples的答案一样。


相关系数进行分析,然后做更大的工作,回归是合理的。
象嘉道2012年

18
此答复仅适用于线性回归,这是最简单的多项式回归
tashuhka

5
注意:r_value是皮尔逊相关系数,而不是R平方。r_squared = r_value ** 2
弗拉基米尔·卢金

52

来自yanl(还有另一个图书馆)sklearn.metrics具有r2_score功能;

from sklearn.metrics import r2_score

coefficient_of_dermination = r2_score(y, p(x))

1
(请注意:“默认值对应于'variance_weighted',自版本0.17起已弃用此行为,将从0.19开始将其更改为'uniform_average'”)
Franck Dernoncourt

4
sklearn中的r2_score可能为负值,这不是正常情况。
刘庆清

为什么r2_score([1,2,3],[4,5,7])= -16
cz

21

我一直在成功使用它,其中x和y类似于数组。

def rsquared(x, y):
    """ Return R^2 where x and y are array-like."""

    slope, intercept, r_value, p_value, std_err = scipy.stats.linregress(x, y)
    return r_value**2

19

我最初发布以下基准测试的目的是推荐numpy.corrcoef,但没有意识到原来的问题已经使用corrcoef,实际上是在询问高阶多项式拟合。我已经使用statsmodels为多项式r平方问题添加了实际的解决方案,并且留下了原始基准测试,尽管离题,但对某人可能很有用。


statsmodels具有r^2直接计算多项式拟合的能力,这是2种方法...

import statsmodels.api as sm
import statsmodels.formula.api as smf

# Construct the columns for the different powers of x
def get_r2_statsmodels(x, y, k=1):
    xpoly = np.column_stack([x**i for i in range(k+1)])    
    return sm.OLS(y, xpoly).fit().rsquared

# Use the formula API and construct a formula describing the polynomial
def get_r2_statsmodels_formula(x, y, k=1):
    formula = 'y ~ 1 + ' + ' + '.join('I(x**{})'.format(i) for i in range(1, k+1))
    data = {'x': x, 'y': y}
    return smf.ols(formula, data).fit().rsquared # or rsquared_adj

为了进一步利用statsmodels,还应该查看拟合的模型摘要,该摘要可以在Jupyter / IPython笔记本中作为丰富的HTML表进行打印或显示。除了,结果对象还提供对许多有用的统计指标的访问rsquared

model = sm.OLS(y, xpoly)
results = model.fit()
results.summary()

以下是我的原始答案,在此我对各种线性回归r ^ 2方法进行了基准测试...

“ 问题”中使用的corrcoef函数r仅针对单个线性回归计算相关系数,因此无法解决r^2高阶多项式拟合的问题。但是,对于它的价值而言,我发现对于线性回归,它确实是最快,最直接的计算方法r

def get_r2_numpy_corrcoef(x, y):
    return np.corrcoef(x, y)[0, 1]**2

这些是我的timeit结果,它通过比较一堆针对1000个随机(x,y)点的方法得出的结果:

  • 纯Python(直接r计算)
    • 1000次循环,最佳3:每个循环1.59毫秒
  • numpy多项式拟合(适用于n次多项式拟合)
    • 1000个循环,最好为3:每个循环326 µs
  • numpy手册(直接r计算)
    • 10000个循环,最好为3:每个循环62.1 µs
  • 脾气暴躁(直接r计算)
    • 10000个循环,最佳3:每个循环56.6 µs
  • Scipy(线性回归r作为输出)
    • 1000个循环,每个循环最好3:676 µs
  • 统计模型(可以执行n次多项式和许多其他拟合)
    • 1000个循环,最佳3:每个循环422 µs

corrcoef方法差于使用numpy方法“手动”计算r ^ 2。它比polyfit方法快5倍以上,比scipy.linregress快12倍左右。只是为了增强numpy为您所做的工作,它比纯python快28倍。我不精通numba和pypy之类的东西,因此其他人必须填补这些空白,但是我认为这对我很有说服力,它corrcoef是计算r简单线性回归的最佳工具。

这是我的基准测试代码。我从Jupyter笔记本复制粘贴(很难称它为IPython笔记本...),因此如果途中发生任何问题,我深表歉意。%timeit magic命令需要IPython。

import numpy as np
from scipy import stats
import statsmodels.api as sm
import math

n=1000
x = np.random.rand(1000)*10
x.sort()
y = 10 * x + (5+np.random.randn(1000)*10-5)

x_list = list(x)
y_list = list(y)

def get_r2_numpy(x, y):
    slope, intercept = np.polyfit(x, y, 1)
    r_squared = 1 - (sum((y - (slope * x + intercept))**2) / ((len(y) - 1) * np.var(y, ddof=1)))
    return r_squared
    
def get_r2_scipy(x, y):
    _, _, r_value, _, _ = stats.linregress(x, y)
    return r_value**2
    
def get_r2_statsmodels(x, y):
    return sm.OLS(y, sm.add_constant(x)).fit().rsquared
    
def get_r2_python(x_list, y_list):
    n = len(x_list)
    x_bar = sum(x_list)/n
    y_bar = sum(y_list)/n
    x_std = math.sqrt(sum([(xi-x_bar)**2 for xi in x_list])/(n-1))
    y_std = math.sqrt(sum([(yi-y_bar)**2 for yi in y_list])/(n-1))
    zx = [(xi-x_bar)/x_std for xi in x_list]
    zy = [(yi-y_bar)/y_std for yi in y_list]
    r = sum(zxi*zyi for zxi, zyi in zip(zx, zy))/(n-1)
    return r**2
    
def get_r2_numpy_manual(x, y):
    zx = (x-np.mean(x))/np.std(x, ddof=1)
    zy = (y-np.mean(y))/np.std(y, ddof=1)
    r = np.sum(zx*zy)/(len(x)-1)
    return r**2
    
def get_r2_numpy_corrcoef(x, y):
    return np.corrcoef(x, y)[0, 1]**2
    
print('Python')
%timeit get_r2_python(x_list, y_list)
print('Numpy polyfit')
%timeit get_r2_numpy(x, y)
print('Numpy Manual')
%timeit get_r2_numpy_manual(x, y)
print('Numpy corrcoef')
%timeit get_r2_numpy_corrcoef(x, y)
print('Scipy')
%timeit get_r2_scipy(x, y)
print('Statsmodels')
%timeit get_r2_statsmodels(x, y)

1
您正在比较3种方法与拟合斜率的比较和回归与3种方法与拟合斜率的比较。
约瑟夫,

是的,我知道很多……但是现在我因为不阅读原始问题并看到它已经使用了corrcoef而感到傻了,并且特别针对高阶多项式解决r ^ 2问题……现在我为发布基准测试感到傻了是出于不同的目的。糟糕...
flutefreak16年

1
我已使用解决了原始问题的解决方案,从而更新了答案statsmodels,并为不必要的线性回归r ^ 2方法的基准测试道歉,我将其作为有趣但又不合时宜的信息。
flutefreak7年

我仍然觉得基准测试很有趣,因为我没想到scipy的linregress不会比statsmodels慢,而statsmodels的工作更多。
约瑟夫,

1
请注意,np.column_stack([x**i for i in range(k+1)])可以x[:,None]**np.arange(k+1)使用numpy的vander函数或使用numpy的vander函数(在列中具有相反的顺序)进行矢量化。
约瑟夫

5

R平方是仅适用于线性回归的统计量。

本质上,它衡量的是线性回归可以解释数据中的多少变化。

因此,您将计算“平方和”,这是每个结果变量与其平均值的总平方偏差。。。

\ sum_ {i}(y_ {i}-y_bar)^ 2

其中y_bar是y的平均值。

然后,您计算“平方回归总和”,这就是您的FITTED值与均值相差多少

\ sum_ {i}(yHat_ {i}-y_bar)^ 2

并找出两者的比例。

现在,要进行多项式拟合,您所要做的就是插入该模型的y_hat,但称该r平方并不准确。

是我发现的一个链接,它对此有所说明。


这似乎是我问题的根源。Excel如何获得多项式拟合与线性回归的不同r平方值?
特拉维斯·比尔

1
您只是从线性回归中给出了Excel拟合,还是从多项式模型中获得了拟合?它将从两个数据数组计算rsq,并假设您从线性模型中得出拟合值。你在给什么卓越的表现?什么是Excel中的“最佳拟合趋势线”命令?
巴尔的摩

它是Excel图形功能的一部分。您可以绘制一些数据,右键单击它,然后从几种不同类型的趋势线中进行选择。可以选择查看线的方程式以及每种类型的r平方值。每种类型的r平方值也不同。
特拉维斯·比尔

@Travis Beale-您将为尝试的每个不同的均值函数获得不同的r平方(除非嵌套了两个模型,并且较大模型中的多余系数全部为0)。因此,当然Excel提供了不同的r平方值。@Baltimark-这是线性回归,因此是r平方的。
leif


5

这是一个使用Python和Numpy 计算加权 r平方的函数(大多数代码来自sklearn):

from __future__ import division 
import numpy as np

def compute_r2_weighted(y_true, y_pred, weight):
    sse = (weight * (y_true - y_pred) ** 2).sum(axis=0, dtype=np.float64)
    tse = (weight * (y_true - np.average(
        y_true, axis=0, weights=weight)) ** 2).sum(axis=0, dtype=np.float64)
    r2_score = 1 - (sse / tse)
    return r2_score, sse, tse

例:

from __future__ import print_function, division 
import sklearn.metrics 

def compute_r2_weighted(y_true, y_pred, weight):
    sse = (weight * (y_true - y_pred) ** 2).sum(axis=0, dtype=np.float64)
    tse = (weight * (y_true - np.average(
        y_true, axis=0, weights=weight)) ** 2).sum(axis=0, dtype=np.float64)
    r2_score = 1 - (sse / tse)
    return r2_score, sse, tse    

def compute_r2(y_true, y_predicted):
    sse = sum((y_true - y_predicted)**2)
    tse = (len(y_true) - 1) * np.var(y_true, ddof=1)
    r2_score = 1 - (sse / tse)
    return r2_score, sse, tse

def main():
    '''
    Demonstrate the use of compute_r2_weighted() and checks the results against sklearn
    '''        
    y_true = [3, -0.5, 2, 7]
    y_pred = [2.5, 0.0, 2, 8]
    weight = [1, 5, 1, 2]
    r2_score = sklearn.metrics.r2_score(y_true, y_pred)
    print('r2_score: {0}'.format(r2_score))  
    r2_score,_,_ = compute_r2(np.array(y_true), np.array(y_pred))
    print('r2_score: {0}'.format(r2_score))
    r2_score = sklearn.metrics.r2_score(y_true, y_pred,weight)
    print('r2_score weighted: {0}'.format(r2_score))
    r2_score,_,_ = compute_r2_weighted(np.array(y_true), np.array(y_pred), np.array(weight))
    print('r2_score weighted: {0}'.format(r2_score))

if __name__ == "__main__":
    main()
    #cProfile.run('main()') # if you want to do some profiling

输出:

r2_score: 0.9486081370449679
r2_score: 0.9486081370449679
r2_score weighted: 0.9573170731707317
r2_score weighted: 0.9573170731707317

这对应于公式mirror):

在此处输入图片说明

其中f_i是来自拟合的预测值,y_ {av}是观测数据的平均值y_i是观测数据值。w_i是应用于每个数据点的权重,通常w_i = 1。SSE是由于误差导致的平方和,SST是平方和。


如果有兴趣的话,R中的代码:https : //gist.github.com/dhimmel/588d64a73fa4fef02c8fmirror


2

这是一个非常简单的python函数,用于假设y和y_hat是熊猫系列,根据实际值和预测值计算R ^ 2:

def r_squared(y, y_hat):
    y_bar = y.mean()
    ss_tot = ((y-y_bar)**2).sum()
    ss_res = ((y-y_hat)**2).sum()
    return 1 - (ss_res/ss_tot)

0

从scipy.stats.linregress来源。他们使用平均平方和方法。

import numpy as np

x = np.array(x)
y = np.array(y)

# average sum of squares:
ssxm, ssxym, ssyxm, ssym = np.cov(x, y, bias=1).flat

r_num = ssxym
r_den = np.sqrt(ssxm * ssym)
r = r_num / r_den

if r_den == 0.0:
    r = 0.0
else:
    r = r_num / r_den

    if r > 1.0:
        r = 1.0
    elif r < -1.0:
        r = -1.0

0

您可以直接执行此代码,这将为您找到多项式,并且将为您找到R值,如果您需要更多说明,可以在下面添加注释。

from scipy.stats import linregress
import numpy as np

x = np.array([1,2,3,4,5,6])
y = np.array([2,3,5,6,7,8])

p3 = np.polyfit(x,y,3) # 3rd degree polynomial, you can change it to any degree you want
xp = np.linspace(1,6,6)  # 6 means the length of the line
poly_arr = np.polyval(p3,xp)

poly_list = [round(num, 3) for num in list(poly_arr)]
slope, intercept, r_value, p_value, std_err = linregress(x, poly_list)
print(r_value**2)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.