我该如何使用Python和Numpy计算R平方?

139
我正在使用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
注意:您只需在计算系数时使用度数。 - Nick Dandoulakis
tydok是正确的。您正在计算x和y的相关性以及y = p_0 + p_1 * x的r平方。请参见下面的答案,其中包含一些应该有效的代码。如果您不介意我问,您的最终目标是什么?您正在进行模型选择(选择要使用的度数)吗?还是其他什么? - leif
@leif -- 这个请求的核心是“像Excel一样做”。从这些答案中我感觉到用户在使用非线性最佳拟合曲线时可能会过多地解读R平方值。尽管如此,我不是一个数学巨匠,但这是所请求的功能。 - Travis Beale
旁注:pandas的corr()函数不是返回r^2皮尔逊系数吗? - habarnam
12个回答

195

虽然回复有些晚了,但如果有人需要一个现成的函数来实现这个功能:

scipy.stats.linregress

即:

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

就像 @Adam Marples 的回答中所说的那样。


1
合理的做法是先用相关系数进行分析,然后再进行更大的工作——回归分析 - 象嘉道
26
这个回复只适用于线性回归,它是最简单的多项式回归。 - tashuhka
32
注意:此处的r_value是皮尔逊相关系数,而不是R平方。r_squared = r_value ** 2。 - Vladimir Lukin

85

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


from sklearn.metrics import r2_score

coefficient_of_dermination = r2_score(y, p(x))

3
默认值对应于“variance_weighted”,自版本0.17以来,此行为已被弃用,并将从0.19开始更改为“uniform_average”。 - Franck Dernoncourt
4
在sklearn中,r2_score可能会是负值,这并不是正常情况。 - Qinqing Liu
14
为什么 r2_score([1,2,3],[4,5,7]) 的结果是 -16 - c z
1
我喜欢的一件事是它不需要训练模型 - 通常我会从在不同环境中训练的模型计算指标。 - Merlin
1
@cz R2的公式为R2= 1 - (SSres/SStot) 这里是计算过程: 数组1的平均值等于2,所以。 ((4-1)^2 + (5-2)^2 + (7-3)^2) / ((1-2)^2 + (2-2)^2 + (3-2)^2) = 17 1 - 17 = -16。 - Walterwhites
1
@cz 我假设我们习惯于看到一个正的R2值,因为真实的最佳拟合线永远不会将点[1, 2, 3]与[4, 5, 7]拟合。 - Nathan Dai

83
numpy.polyfit文档中可以看出,它是用于拟合线性回归的。具体来说,numpy.polyfit使用度数“d”对均值函数进行线性回归拟合。
E(y|x) = p_d * x**d + p_{d-1} * x **(d-1) + ... + p_1 * x + p_0
因此,您只需要计算适合该模型的R-squared。维基百科线性回归页面提供了详细信息。您需要计算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-squared,但以下内容应该是正确的。

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
26
根据维基百科页面http://en.wikipedia.org/wiki/Coefficient_of_determination,R^2的最一般定义是`R^2 = 1 - SS_err/SS_tot,其中R^2 = SS_reg/SS_tot`只是一个特殊情况。 - LWZ
非线性最小二乘函数的R平方怎么样? - bonCodigo

33

我已经成功地使用了这个,其中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

2
这不是皮尔逊相关系数,而是相关系数的平方——完全不同的东西。 - liorr
@liorr 我的理解是决定系数是相关系数的平方。 - Adam Marples
我认为这仅适用于使用线性回归的情况:https://en.wikipedia.org/wiki/Coefficient_of_determination 其中一类这样的情况包括简单线性回归,其中使用r2而不是R2。当仅包括截距时,r2就是观测结果和观测预测值之间的样本相关系数(即r)的平方。 - liorr
我认为你混淆了线性回归和拟合任意次数的多项式的线性模型。你正在使用线性回归,但只能拟合一次多项式。因此,你并没有真正回答问题,这个问题是关于“任意次数的最佳拟合多项式”的。我建议你编辑你的答案,以反映它只适用于一次多项式(即模型函数a + bx)。 - liorr
3
啊,是的,我没有仔细阅读问题。为自己辩护,那是9年前的事情,而且我现在还没有。 - Adam Marples
显示剩余2条评论

28

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


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)点上的timeit结果:
- 纯Python (直接r计算) - 1000次循环,3次取最优: 每次1.59毫秒 - Numpy polyfit (适用于n次多项式拟合) - 1000次循环,3次取最优: 每次326微秒 - Numpy Manual (直接r计算) - 10000次循环,3次取最优: 每次62.1微秒 - Numpy corrcoef (直接r计算) - 10000次循环,3次取最优: 每次56.6微秒 - Scipy (使用线性回归,输出r) - 1000次循环,3次取最优: 每次676微秒 - Statsmodels (可以进行n次多项式和其他许多拟合) - 1000次循环,3次取最优: 每次422微秒

corrcoef方法比使用numpy方法手动计算r^2要快。 它比polyfit方法快5倍以上,比scipy.linregress快约12倍。 仅为了加强对numpy为您完成的工作的理解,它比纯Python快28倍。 我不太精通numba和pypy之类的东西,因此其他人必须填补这些空白,但我认为这已经足以让我相信corrcoef是计算简单线性回归的最佳工具。

这是我的基准测试代码。 我从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)

2021年7月28日基准测试结果。(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)

1
您正在比较三种方法,其中包括拟合斜率和回归,以及不拟合斜率的三种方法。 - Josef
是的,我已经知道了...但现在我感到有些傻,因为我没有阅读原始问题并看到它已经使用了corrcoef,并且专门针对更高阶多项式处理r ^ 2...现在我为发布我的基准测试感到有些傻。糟糕... - flutefreak7
1
我已经使用statsmodels更新了我的答案,并提供了原始问题的解决方案,同时为线性回归r ^ 2方法的无意义基准测试道歉,我将其保留为有趣但不相关的信息。 - flutefreak7
1
我仍然觉得基准测试很有趣,因为我没有预料到scipy的linregress比做更通用工作的statsmodels要慢。 - Josef
2
注意,np.column_stack([x**i for i in range(k+1)]) 可以在 numpy 中向量化处理,使用 x[:,None]**np.arange(k+1) 或者使用 numpy 的 vander 函数,该函数的列顺序是相反的。 - Josef
显示剩余3条评论

10

这是一个使用 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

这对应于 公式 (镜像):

enter image description here

f_i是拟合值,y_{av}是观测数据的平均值,y_i是观测数据的值。w_i是应用于每个数据点的加权值,通常w_i=1。SSE是由误差引起的平方和,而SST是总平方和。


如果您感兴趣,这是R代码: https://gist.github.com/dhimmel/588d64a73fa4fef02c8f (镜像)

7
这里有一个非常简单的Python函数,用于根据实际值和预测值计算R^2,假设y和y_hat是Pandas系列:
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是一个优化问题,对于最佳拟合线的斜率和偏移有多个解决方案。 - russian_spy
上述函数适用于任何模型,线性、非线性、机器学习等等... 它只关注预测值和实际值之间的差异。每个模型通常会创建不同的R^2。拟合给定模型涉及通过改变模型的参数来最小化R^2。对于具有一个自变量和一个因变量的曲线进行直线拟合具有唯一解(局部最小值==全局最小值)。更复杂的模型,特别是具有额外自变量的模型,可能有许多局部最小值,并且找到全局最小值可能非常困难。 - Michel Floyd

6

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

本质上,它衡量了线性回归可以解释数据变化的程度。

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

formula1

其中y_bar是y的平均值。

然后,您需要计算“回归平方和”,即你的拟合值与平均值之间的差异程度。

formula2

并计算这两者的比率。
现在,对于多项式拟合,您只需要插入该模型中的y_hat即可,但称其为r-squared并不准确。
我找到了一个链接,它稍微谈到了这个问题。

这似乎是我的问题的根源。那么,Excel如何在多项式拟合和线性回归中获得不同的R平方值呢? - Travis Beale
1
你只是给Excel提供了线性回归和多项式模型的拟合吗?它将从两个数据数组计算R平方,并假定您正在提供线性模型的拟合。你给Excel什么?在Excel中,“最佳拟合趋势线”命令是什么? - Baltimark
这是 Excel 绘图功能的一部分。您可以绘制一些数据,右键单击它,然后从几种不同类型的趋势线中选择。还有一个选项可以查看每种类型的线方程以及 r-squared 值。每种类型的 r-squared 值也不同。 - Travis Beale
@Travis Beale -- 你尝试不同的均值函数时,每个模型的R平方值都会不同(除非两个模型是嵌套的,并且较大模型中的额外系数全部为0)。因此,Excel给出了不同的R平方值。@Baltimark -- 这是线性回归,所以它是R平方。 - leif

5

维基百科关于R平方的文章表明,它可以用于一般的模型拟合,而不仅仅是线性回归。


1
这是一个关于非线性回归中R2问题的良好描述:http://blog.minitab.com/blog/adventures-in-statistics/why-is-there-no-r-squared-for-nonlinear-regression - Tickon

3

使用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²可能更适合你的需要,因为它是最小二乘拟合,比简单求和的r²更好。Numpy 不怕称其为“corrcoef”,这预设皮尔森系数是事实上的相关系数。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接