本ポストは、こちらの続きです。

AICを利用してloess{stats}の最適spanを選択
const typesetMath = (el) => { if (window.MathJax) { // MathJax Typeset window.MathJax.typeset(); } else if (window.katex...
LOESS における Smoothing Matrix(L) および、Rの関数 stats::loessを利用して得られる4つの統計量 trace.hat、one.delta、two.delta、enp をスクラッチで求めます。
5つの統計量の関係
- \(L\)
- \(\hat{\mathbf{y}} = L\mathbf{y}\)
- hat matrix:観測値から予測値への線形写像
- Smoothing Matrix / 平滑化行列
- \(\mathrm{tr}(L)\)
- \(\sum_j L_{jj}\)
- 有効パラメータ数(当てはめ側の自由度)
trace.hat
- \(\delta_1\)
- \(\mathrm{tr}\!\left[(I-L)(I-L)^\top\right]\)
- 残差の有効自由度(分散推定の分母)
- \(\delta_2\)
- \(\mathrm{tr}\!\left[((I-L)(I-L)^\top)^2\right]\)
- 自由度の補正係数(信頼区間の精度向上)
- \(\text{enp}\)
- \(\delta_1 + 2\,\mathrm{tr}(L) - N\)
- 等価パラメータ数(モデルの複雑さの総合指標)
- Equivalent Number of Parameters
δ1、δ2、trace hat、enp および L の算出アルゴリズム
コードは build_hat_matrix()、lowesc_R()、そして enpの計算 の 3 段階で構成されています。
アルゴリズム全体の流れは以下の通りです。
入力: x(説明変数), y(応答変数), weights, span, degree
│
├─ [build_hat_matrix] ─────────────────────────────────
│ j = 1, 2, ..., N のループ
│ 1. 全点との二乗距離 dist2 を計算
│ 2. 近傍 nf 点のインデックス psi を選択
│ 3. バンド幅 rho = dist2[psi[nf]]
│ 4. tricube 重み w_kern を計算(sqrt 付き)
│ 5. 重み付きデザイン行列 B を構築
│ 6. WLS: L[j, psi] = xj (B'B)^{-1} B' * w_kern
│ → N×N の hat matrix L
│
├─ [lowesc_R] ─────────────────────────────────────────
│ 7. LL = (L - I)(L - I)'
│ 8. trace.hat = tr(L)
│ δ1 = tr(LL)
│ δ2 = tr(LL²)
│ → trace.hat, δ1, δ2
│
└─ [enp の計算] ───────────────────────────────────────
9. enp = δ1 + 2 × trace.hat - N
→ enpRコード
対応するソースコード
hat matrix の構築 (loessf.f の ehg127 / ehg136 に対応)
- 引数:
-
x: 説明変数ベクトルまたは行列 (N × D) -
weights: case weights (長さ N)。NULL なら全て 1 -
span: バンド幅パラメータ (0 < span <= 1) -
degree: 多項式次数 (1L または 2L)
-
- 戻り値:
- N×N の hat matrix L
build_hat_matrix <- function(x, weights = NULL, span = 0.75, degree = 2L) {
x <- as.matrix(x)
N <- nrow(x)
if (is.null(weights)) weights <- rep(1.0, N)
# 近傍点数 nf の計算
nf <- min(N, as.integer(floor(N * span + 1e-5)))
L <- matrix(0.0, N, N)
for (j in seq_len(N)) {
q <- x[j, , drop = TRUE]
# 二乗距離の計算
dist2 <- rowSums(sweep(x, 2, q, "-")^2)
# 近傍 nf 点の選択(partial sort)
psi <- order(dist2)[seq_len(nf)]
# バンド幅 rho = nf 番目二乗距離 × max(1, span)
rho <- dist2[psi[nf]] * max(1.0, span)
if (rho == 0) rho <- .Machine$double.eps
# tricube カーネル重みの計算(ehg127 の 2 ループを忠実に再現)
u <- sqrt(dist2[psi] / rho) # u_i = dist_i / sqrt(rho)
w_kern <- sqrt(weights[psi] * pmax(0, 1 - u^3)^3) # sqrt(rw * tricube)
# 重み付きデザイン行列 B = W^{1/2} X (nf × p) の構築
dx <- x[psi, 1, drop = TRUE] - q[1]
X_raw <- if (degree == 1L) cbind(1, dx) else cbind(1, dx, dx^2)
B <- X_raw * w_kern # 各行 i に w_kern[i] を乗算
# WLS による hat matrix 行 j の計算
# B^T B = X^T W X (W = diag(w_kern^2))
BtB <- crossprod(B)
BtB_inv <- tryCatch(
solve(BtB),
error = function(e) {
# ランク落ち時は Moore-Penrose 疑似逆行列
s <- svd(BtB)
tol <- max(dim(BtB)) * .Machine$double.eps * max(s$d)
s$v %*% diag(ifelse(s$d > tol, 1 / s$d, 0), length(s$d)) %*% t(s$u)
}
)
# 評価点 j は psi の中で距離 0 の点(必ず含まれる)
j_idx <- which(psi == j)
xj <- matrix(X_raw[j_idx, ], nrow = 1) # 1 × p
# L[j, psi] = xj %*% (X^T W X)^{-1} X^T W
# = xj %*% BtB_inv %*% t(B) * w_kern
# t(B) * w_kern は各列 i に w_kern[i] を乗算 → X^T W に等しい
L[j, psi] <- as.numeric(xj %*% BtB_inv %*% t(B) * w_kern)
}
L
}lowesc に対応する計算
- 引数:
-
L: hat matrix (N×N)
-
- 戻り値:
- list(trl, delta1, delta2)
lowesc_R <- function(L) {
N <- nrow(L)
# M = L - I = -(I - L)
M <- L - diag(N)
# LL = M M^T = (I-L)(I-L)^T
LL <- M %*% t(M)
# trace(L) → trace.hat
trl <- sum(diag(L))
# trace(LL) → one.delta = Σ_i ||(I-L)[i,:]||²
delta1 <- sum(diag(LL))
# trace(LL^2) → two.delta
delta2 <- sum(diag(LL %*% LL))
list(trl = trl, delta1 = delta1, delta2 = delta2)
}メイン関数
compute_loess_stats_scratch <- function(y, x, weights = NULL,
span = 0.75, degree = 2L) {
N <- length(y)
L <- build_hat_matrix(x, weights, span, degree)
res <- lowesc_R(L)
# enp の計算
enp <- res$delta1 + 2 * res$trl - N
list(
one_delta = res$delta1,
two_delta = res$delta2,
trace_hat = res$trl,
enp = enp,
L = L
)
}Rの関数loessと結果が一致するか検証
seed <- 20260611
set.seed(seed)
n <- 250
x <- seq(0, 2 * pi, length.out = n)
y <- sin(x) + rnorm(n, 0, 0.2)
w <- rep(1, n)
res <- compute_loess_stats_scratch(y, x, w, span = 0.75, degree = 2L)
cat("スクラッチ one.delta =", res$one_delta, "\n")
cat("スクラッチ two.delta =", res$two_delta, "\n")
cat("スクラッチ trace.hat =", res$trace_hat, "\n")
cat("スクラッチ enp =", res$enp, "\n")
cat("-----------------\n")
fit <- loess(y ~ x, span = 0.75, degree = 2, weights = w,
control = loess.control(surface = "direct",
statistics = "exact",
trace.hat = "exact"))
cat("公式 one.delta =", fit$one.delta, "\n")
cat("公式 two.delta =", fit$two.delta, "\n")
cat("公式 trace.hat =", fit$trace.hat, "\n")
cat("公式 enp =", fit$enp, "\n")スクラッチ one.delta = 244.916
スクラッチ two.delta = 244.6779
スクラッチ trace.hat = 4.743286
スクラッチ enp = 4.402545
-----------------
公式 one.delta = 244.916
公式 two.delta = 244.6779
公式 trace.hat = 4.743286
公式 enp = 4.402545 4つの統計量は全て一致しています。
以上です。
