我刚刚意识到2年前有一个非常相关的问题:从R的poly()函数中提取正交多项式系数?。那里的答案仅仅解释了predict.poly
的作用,但是我的答案给出了完整的图片。
第一部分: poly
如何表示正交多项式
我理解的正交多项式具有如下形式:
y(x) = a1 + a2(x - c1) + a3(x - c2)(x - c3) + a4(x - c4)(x - c5)(x - c6)... 直到所需项数为止
不,没有这样干净的形式。poly()
生成的是首项系数为一的正交多项式,可以通过以下递归算法表示。这就是predict.poly
生成线性预测矩阵的方式。令人惊讶的是,poly
本身并不使用这样的递归,而是使用暴力方法:对普通多项式的模型矩阵进行QR分解以获得正交空间。然而,这等价于递归。
![enter image description here](https://istack.dev59.com/J27ak.webp)
第二部分:解释 poly()
的输出
让我们来看一个例子。以您帖子中的x
为例:
X <- poly(x, degree = 5)
以下是各个属性的含义:
alpha[1]
表示 x
的均值 x_bar
,即中心位置;
alpha - alpha[1]
得到 alpha0
, alpha1
, ..., alpha4
(alpha5
在 poly
返回矩阵 X
之前已经被计算出来,但由于不会在 predict.poly
中使用而被舍弃);
norm2
的第一个值始终为1,其余部分为 l0
, l1
, ..., l5
,它们表示矩阵 X
的列平方和;其中的 l0
是被舍弃的项 P0(x - x_bar)
的列平方和,总是等于 n
(即 x
的长度),而第一个 1
只是为了使递归在 predict.poly
中继续进行;
beta0
, beta1
, beta2
, ..., beta_5
没有被返回,但可以通过 norm2[-1] / norm2[-length(norm2)]
来计算。
第三部分:使用 QR 分解和递归算法实现 poly
正如之前提到的,poly
不使用递归,而 predict.poly
则使用了递归。个人认为这种设计不太一致,不太容易理解其逻辑和原因。在此我提供一个名为 my_poly
的函数,如果 QR = FALSE
,则使用递归生成矩阵;当 QR = TRUE
时,则是类似但不完全相同的实现方式。代码有详细的注释,有助于您理解两种方法。
my_poly <- function (x, degree = 1, QR = TRUE) {
if (length(unique(x)) < degree)
stop("insufficient unique data points for specified degree!")
centre <- mean(x)
x <- x - centre
if (QR) {
QR <- qr(outer(x, 0:degree, "^"))
X <- qr.qy(QR, diag(diag(QR$qr), length(x), degree + 1))[, -1, drop = FALSE]
X2 <- X * X
norm2 <- colSums(X * X)
alpha <- drop(crossprod(X2, x)) / norm2
beta <- norm2 / (c(length(x), norm2[-degree]))
colnames(X) <- 1:degree
}
else {
beta <- alpha <- norm2 <- numeric(degree)
X <- matrix(x, nrow = length(x), ncol = degree, dimnames = list(NULL, 1:degree))
norm2[1] <- new_norm <- drop(crossprod(x))
alpha[1] <- sum(x ^ 3) / new_norm
beta[1] <- new_norm / length(x)
if (degree > 1L) {
old_norm <- new_norm
X[, 2] <- Xi <- (x - alpha[1]) * X[, 1] - beta[1]
norm2[2] <- new_norm <- drop(crossprod(Xi))
alpha[2] <- drop(crossprod(Xi * Xi, x)) / new_norm
beta[2] <- new_norm / old_norm
old_norm <- new_norm
i <- 3
while (i <= degree) {
X[, i] <- Xi <- (x - alpha[i - 1]) * X[, i - 1] - beta[i - 1] * X[, i - 2]
norm2[i] <- new_norm <- drop(crossprod(Xi))
alpha[i] <- drop(crossprod(Xi * Xi, x)) / new_norm
beta[i] <- new_norm / old_norm
old_norm <- new_norm
i <- i + 1
}
}
}
scale <- sqrt(norm2)
X <- X * rep(1 / scale, each = length(x))
attr(X, "coefs") <- list(centre = centre, scale = scale, alpha = alpha[-degree], beta = beta[-degree])
X
}
第四部分:解释my_poly
的输出X <- my_poly(x, 5, FALSE)
因此,生成的矩阵与poly
生成的矩阵相同,因此不予考虑。但是属性不同。
my_poly
返回更明显的构造信息:
centre
给出 x_bar = mean(x)
;
scale
给出列范数(通过poly
返回的norm2
的平方根);
alpha
给出alpha1
,alpha2
,alpha3
,alpha4
;
beta
给出beta1
,beta2
,beta3
,beta4
。
第五节:用于my_poly
的预测例程
由于 my_poly
返回不同的属性,因此 stats:::predict.poly
与 my_poly
不兼容。这是适当的例程 my_predict_poly
:
my_predict_poly <- function (X, x) {
coefs <- attr(X, "coefs")
centre <- coefs$centre
alpha <- coefs$alpha
beta <- coefs$beta
degree <- ncol(X)
x <- x - coefs$centre
X <- matrix(x, length(x), degree, dimnames = list(NULL, 1:degree))
if (degree > 1L) {
X[, 2] <- (x - alpha[1]) * X[, 1] - beta[1]
i <- 3
while (i <= degree) {
X[, i] <- (x - alpha[i - 1]) * X[, i - 1] - beta[i - 1] * X[, i - 2]
i <- i + 1
}
}
X * rep(1 / coefs$scale, each = length(x))
}
考虑一个例子:
set.seed(0); x1 <- runif(5, min(x), max(x))
和
stats:::predict.poly(poly(x, 5), x1)
my_predict_poly(my_poly(x, 5, FALSE), x1)
给出完全相同的结果预测矩阵:
请注意,预测程序只采用现有的构造信息,而不重构多项式。
第6节:将poly
和predict.poly
视为黑盒子即可
通常情况下,不需要理解其中的所有细节。对于统计建模来说,了解poly
构建模型拟合的多项式基础并在lmObject$coefficients
中找到其系数即可。进行预测时,用户无需调用predict.poly
,因为predict.lm
会自动调用它。因此,可以将poly
和predict.poly
视为黑盒子,这完全没有问题。
poly
的输出与此直接相关,但我无知于poly
拟合它们的不同方式。再次感谢! - pyg