Python 多重简单线性回归

7 投票
1 回答
4799 浏览
提问于 2025-04-17 23:53

请注意,这不是关于多重回归的问题,而是关于在Python/NumPy(2.7)中多次进行简单(单变量)回归的问题。

我有两个 mn 列的数组 xy。这些行是相互对应的,每一对就是一组测量的 (x,y) 点。也就是说,plt.plot(x.T, y.T, '.') 会绘制出 m 个数据集/测量结果。

我想知道进行 m 次线性回归的最佳方法是什么。目前我是在循环每一行,并使用 scipy.stats.linregress()。假设我不想用矩阵的线性代数方法,而是想用这个函数,或者类似的黑箱函数。我可以尝试 np.vectorize,但文档显示它也是在循环。

经过一些实验,我还发现可以使用列表推导和 map() 来得到正确的结果。我把这两种解决方案放在下面。在IPython中,使用一个小数据集(已注释掉)时,`%%timeit` 的返回结果是:

(loop) 1000 loops, best of 3: 642 µs per loop
(map) 1000 loops, best of 3: 634 µs per loop

为了放大这个效果,我制作了一个更大的随机数据集(维度为 trials x trials):

(loop, trials = 1000)  1 loops, best of 3: 299 ms per loop
(loop, trials = 10000) 1 loops, best of 3: 5.64 s per loop
(map, trials = 1000)   1 loops, best of 3: 256 ms per loop
(map, trials = 10000)  1 loops, best of 3: 2.37 s per loop

在一个非常大的数据集上,这样的速度提升还不错,但我原本期待能更快一些。有没有更好的方法呢?

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
np.random.seed(42)
#y = np.array(((0,1,2,3),(1,2,3,4),(2,4,6,8)))
#x = np.tile(np.arange(4), (3,1))
trials = 1000
y = np.random.rand(trials,trials)
x = np.tile(np.arange(trials), (trials,1))
num_rows = shape(y)[0]
slope = np.zeros(num_rows)
inter = np.zeros(num_rows)
for k, xrow in enumerate(x):
    yrow = y[k,:]
    slope[k], inter[k], t1, t2, t3 = stats.linregress(xrow, yrow)
#plt.plot(x.T, y.T, '.')
#plt.hold = True
#plt.plot(x.T, x.T*slope + intercept)
# Can the loop be removed?
tempx = [x[k,:] for k in range(num_rows)]
tempy = [y[k,:] for k in range(num_rows)]
results = np.array(map(stats.linregress, tempx, tempy))
slope_vec = results[:,0]
inter_vec = results[:,1]
#plt.plot(x.T, y.T, '.')
#plt.hold = True
#plt.plot(x.T, x.T*slope_vec + inter_vec)
print "Slopes equal by both methods?: ", np.allclose(slope, slope_vec)
print "Inters equal by both methods?: ", np.allclose(inter, inter_vec)

1 个回答

3

单变量线性回归其实很简单,手动把它转成向量形式也不难:

def multiple_linregress(x, y):
    x_mean = np.mean(x, axis=1, keepdims=True)
    x_norm = x - x_mean
    y_mean = np.mean(y, axis=1, keepdims=True)
    y_norm = y - y_mean

    slope = (np.einsum('ij,ij->i', x_norm, y_norm) /
             np.einsum('ij,ij->i', x_norm, x_norm))
    intercept = y_mean[:, 0] - slope * x_mean[:, 0]

    return np.column_stack((slope, intercept))

这里用一些虚构的数据来演示:

m = 1000
n = 1000
x = np.random.rand(m, n)
y = np.random.rand(m, n)

这样做的效率比你用循环的方法要高很多:

%timeit multiple_linregress(x, y)
100 loops, best of 3: 14.1 ms per loop

撰写回答