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

Posted

技术标签:

【中文标题】如何使用 Python 和 Numpy 计算 r 平方?【英文标题】:How do I calculate r-squared using Python and Numpy? 【发布时间】:2010-10-27 23:27:39 【问题描述】:

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

这很有效,但我还想计算 r(相关系数)和 r-squared(确定系数)。我将我的结果与 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

【问题讨论】:

注意:您只在计算系数时使用度数。 tydok 是正确的。您正在计算 x 和 y 的相关性以及 y=p_0 + p_1 * x 的 r 平方。请参阅下面的我的答案以获取一些应该工作的代码。如果你不介意我问,你的最终目标是什么?你在做模型选择(选择使用什么程度)?还是别的什么? @leif -- 请求归结为“像 Excel 那样做”。我从这些答案中感觉到,在使用非线性最佳拟合曲线时,用户可能对 r 平方值的解读过多。尽管如此,我不是数学向导,这是要求的功能。 附带问题:pandas corr() 函数不返回 r^"2 pearson 系数吗? 【参考方案1】:

R-squared 是一种仅适用于线性回归的统计量。

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

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

其中 y_bar 是 y 的平均值。

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

并找出这两者的比率。

现在,多项式拟合所需要做的就是插入该模型中的 y_hat,但称其为 r-squared 并不准确。

Here 是我发现的一个链接。

【讨论】:

这似乎是我问题的根源。 Excel 如何为多项式拟合与线性回归获得不同的 r 平方值? 你只是给excel一个线性回归的拟合,以及一个多项式模型的拟合吗?它将从两个数据数组中计算 rsq,并假设您从线性模型中对其进行拟合。你给excel什么?什么是 Excel 中的“最适合趋势线”命令? 它是 Excel 绘图功能的一部分。您可以绘制一些数据,右键单击它,然后从几种不同类型的趋势线中进行选择。可以选择查看线的方程以及每种类型的 r 平方值。每种类型的 r 平方值也不同。 @Travis Beale -- 你会为你尝试的每个不同的平均函数得到一个不同的 r 平方(除非两个模型是嵌套的并且较大模型中的额外系数都为 0) .所以当然 Excel 给出了不同的 r 平方值。 @Baltimark - 这是线性回归,所以它是 r 平方的。【参考方案2】:

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

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

因此,您只需要计算该拟合的 R 平方即可。 linear regression 上的***页面提供了完整的详细信息。您对 R^2 感兴趣,可以通过多种方式计算,最简单的可能是

SST = Sum(i=1..n) (y_i - y_bar)^2
s-s-reg = Sum(i=1..n) (y_ihat - y_bar)^2
Rsquared = s-s-reg/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)
    s-s-reg = 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'] = s-s-reg / sstot

    return results

【讨论】:

我只想指出,使用 numpy 数组函数而不是列表理解会更快,例如numpy.sum((yi - ybar)**2) 更易阅读 根据wiki页面en.wikipedia.org/wiki/Coefficient_of_determination,R^2的最一般定义是R^2 = 1 - SS_err/SS_totR^2 = SS_reg/SS_tot只是一个特例。 非线性最小二乘函数的 R 平方怎么样?【参考方案3】:

r-squareds 上的***文章表明它可以用于一般模型拟合,而不仅仅是线性回归。

【讨论】:

这里很好地描述了 R2 用于非线性回归的问题:blog.minitab.com/blog/adventures-in-statistics/…【参考方案4】:

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

scipy.stats.linregress

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

就像@Adam Marples 的回答一样。

【讨论】:

相关系数分析是合理的,然后做更大的工作,回归 这个回复只适用于线性回归,是最简单的多项式回归 注意:这里的 r_value 是 Pearson 相关系数,而不是 R 平方。 r_squared = r_value**2【参考方案5】:

我一直在成功地使用它,其中 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

【讨论】:

这不是佩拉森的决定系数,而是相关系数的平方——完全是另外一回事。 @liorr 我的理解是决定系数是相关系数的平方 我认为这仅在使用线性回归时才是正确的:en.wikipedia.org/wiki/Coefficient_of_determination“此类情况包括使用 r2 而不是 R2 的简单线性回归。当仅包括截距时,则r2 只是观察到的结果和观察到的预测值之间的样本相关系数(即 r)的平方。” @liorr 我在我的答案 scipy.stats.linregress 中使用线性回归中的 r**2,所以它是正确的 啊,是的,我没有正确阅读这个问题。在我的辩护中,那是 9 年前的事了,但我还没有。【参考方案6】:

来自 yanl (yet-another-library) sklearn.metrics 有一个 r2_score 函数;

from sklearn.metrics import r2_score

coefficient_of_dermination = r2_score(y, p(x))

【讨论】:

(注意:“默认值对应于‘variance_weighted’,此行为自0.17版起已弃用,将从0.19开始更改为‘uniform_average’”) r2_score in sklearn 可能是负值,这不是正常情况。 为什么是r2_score([1,2,3],[4,5,7]) = -16 我喜欢的一点是它不需要训练模型——我经常从在不同环境中训练的模型计算指标。【参考方案7】:

我最初发布下面的基准是为了推荐numpy.corrcoef,愚蠢地没有意识到原来的问题已经使用corrcoef,实际上是在询问高阶多项式拟合。我使用 statsmodels 为多项式 r-squared 问题添加了一个实际解决方案,并且我保留了原始基准,这些基准虽然离题,但可能对某人有用。


statsmodels 可以直接计算多项式拟合的r^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

这些是我对 1000 个随机 (x, y) 点的一堆方法进行比较的时间结果:

纯Python(直接r计算) 1000 次循环,3 次中的最佳:每个循环 1.59 毫秒 Numpy polyfit(适用于 n 次多项式拟合) 1000 次循环,3 次中的最佳:每个循环 326 µs Numpy手册(直接r计算) 10000 次循环,3 次中的最佳:每个循环 62.1 µs numpy corrcoef(直接r计算) 10000 次循环,3 次中的最佳:每个循环 56.6 µs Scipy(以r 作为输出的线性回归) 1000 次循环,3 次中的最佳:每个循环 676 µs Statsmodels(可以进行 n 次多项式和许多其他拟合) 1000 次循环,3 次中的最佳:每个循环 422 µs

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

这是我的基准测试代码。我从 Jupyter Notebook 复制粘贴(很难不称它为 IPython Notebook...),所以如果途中出现任何问题,我深表歉意。 %timeit 魔术命令需要 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)

7/28/21 基准测试结果。 (Python 3.7、numpy 1.19、scipy 1.6、statsmodels 0.12)

Python
2.41 ms ± 180 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Numpy polyfit
318 µs ± 44.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Numpy Manual
79.3 µs ± 4.05 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Numpy corrcoef
83.8 µs ± 1.37 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Scipy
221 µs ± 7.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Statsmodels
375 µs ± 3.63 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

【讨论】:

您正在比较 3 种拟合斜率的方法和回归与 3 种不拟合斜率的方法。 是的,我知道这么多......但现在我觉得很傻,因为没有阅读原始问题并看到它已经使用 corrcoef 并且专门针对高阶多项式处理 r^2...... 现在我为不同目的发布我的基准感到愚蠢。哎呀... 我已经使用statsmodels 更新了我对原始问题的解决方案的答案,并为线性回归 r^2 方法的不必要基准测试道歉,我一直认为它很有趣,但离题信息。 我仍然觉得基准测试很有趣,因为我没想到 scipy 的 linregress 会比 statsmodels 更通用的工作。 注意,np.column_stack([x**i for i in range(k+1)]) 可以在 numpy 中使用 x[:,None]**np.arange(k+1) 或使用 numpy 的 vander 函数进行矢量化,这些函数在列中具有相反的顺序。【参考方案8】:

这是一个用 Python 和 Numpy 计算 加权 r-squared 的函数(大部分代码来自 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

这对应于formula(mirror):

其中 f_i 是拟合的预测值,y_av 是观测数据的平均值 y_i 是观测数据值。 w_i 是应用于每个数据点的权重,通常 w_i=1。 SSE 是误差平方和,SST 是总平方和。


如果有兴趣,R中的代码:https://gist.github.com/dhimmel/588d64a73fa4fef02c8f (mirror)

【讨论】:

【参考方案9】:

来自 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

【讨论】:

【参考方案10】:

这是一个非常简单的 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)

【讨论】:

这个公式给出的答案与非平凡数据的 numpy 模块不同。这可能是因为 r_squared 是一个优化问题,对最佳拟合线的斜率和偏移有多种解决方案。 上述函数适用于任何模型,线性、非线性、ML 等……它只查看预测值与实际值之间的差异。每个模型通常会创建不同的 R^2。拟合给定模型涉及通过改变模型的参数来最小化 R^2。具有一个自变量和一个因变量的曲线的直线拟合具有唯一解(局部最小值 == 全局最小值)。更复杂的模型,尤其是带有额外自变量的模型,可能有许多局部最小值,而找到全局最小值可能非常困难。【参考方案11】:

您可以直接执行此代码,这将找到您的多项式,并会找到您的 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)

【讨论】:

【参考方案12】:

使用numpy模块(在python3中测试):

import numpy as np
def linear_regression(x, y): 
    coefs = np.polynomial.polynomial.polyfit(x, y, 1)
    ffit = np.poly1d(coefs)
    m = ffit[0]
    b = ffit[1] 
    eq = 'y = x + '.format(round(m, 3), round(b, 3))
    rsquared = np.corrcoef(x, y)[0, 1]**2
    return rsquared, eq, m, b

rsquared, eq, m, b = linear_regression(x,y)
print(rsquared, m, b)
print(eq)

输出:

0.013378252355751777 0.1316331351105754 0.7928782850418713 
y = 0.132x + 0.793

注意:r² ≠ R² r² 称为“确定系数” R² 是皮尔逊系数的平方

R²,正式合并为 r²,可能是您想要的,因为它是最小二乘拟合,比 r² 的简单分数要好。 Numpy 不怕称其为“corrcoef”,这假定 Pearson 是事实上的相关系数。

【讨论】:

我发布了这个解决方案,因为***文章公式给出的结果与 numpy 解决方案不同。我相信 numpy 模块是正确的,因为 wikipedia 公式不考虑存在多个解决方案(最佳拟合线的不同斜率和偏移量),并且 numpy 显然解决了实际的优化问题,而不仅仅是计算总和的一小部分。 [simple] wikipedia 公式错误的证据是它产生负 r_squared 值,这意味着它为非平凡数据的最佳拟合线提出了错误的斜率。

以上是关于如何使用 Python 和 Numpy 计算 r 平方?的主要内容,如果未能解决你的问题,请参考以下文章

计算机视觉OpenCV 4高级编程与项目实战(Python版):使用NumPy创建随机雪花点图像

python numpy基础 数组和矢量计算

如何将围绕 C++ 函数的 R 包装器转换为 Python/Numpy

浮点数的乘法在 Numpy 和 R 中给出不同的结果

使用 numpy 和 sklearn 计算 R^2(确定系数)给出不同的结果

Numpy科学计算从放弃到入门