R 小筆記 – 2

R 真是不一般, 首先它不像別的語言, index 是從 0 開始. 而是從 1 開始. 再來呢, 很多地方的 1 看起來是 1 但不是 1; 看起來是 0 但也不是 0; 2 也不是平常的 2. 底下舉例說明.

上次說到用 X predict Y, 像是身高預測. 因為最低身高不是 0, 這條 minimum error 的線是有截距的. 此時一般線性迴歸的斜率公式是:

  • 模型:y = α + βx
  • 斜率:β = r(x,y) × sd(y)/sd(x)

假設強制要求通過原點, 他的迴歸如下.

  • 模型:y = βx(無截距項)
  • 斜率:β = Σ(xy)/Σ(x²)

最後, 假設原本是有截距的迴歸, 但我把它的數據先做正規化, 那它會跟誰一樣嗎?

x <- c(1.2, 2.8, 3.5, 1.9, 4.1, 2.3, 3.8, 1.6, 2.9, 3.2)
y <- c(2.4, 5.1, 6.8, 3.2, 7.9, 4.1, 7.2, 2.8, 5.5, 6.1)

# 標準化(z-score normalization)
x_norm <- (x - mean(x)) / sd(x)
y_norm <- (y - mean(y)) / sd(y)

# 方法0:一般線性迴歸斜率公式
slope0 <- cor(x, y) * sd(y) / sd(x)

# 方法1:通過原點迴歸
slope1 <- sum(x * y) / sum(x^2)

# 方法2:先正規化後當作一般線性迴歸
slope2 <- cor(x_norm, y_norm) * sd(y_norm) / sd(x_norm) 

cat("有截距 - 不通過原點迴歸:", slope0, "\n")
cat("通過原點迴歸:", slope1, "\n")
cat("標準化後做一般回歸:", slope2, "\n")

結果我們會得到 slope0 ≈ 1.85, slope1 ≈ 1.94, slope2 ≈ 0.93, 三個都不一樣.

方法 0 和方法 2 當中, 因為按照定義 :

sd(x_norm) = sd(y_norm) = 1, mean = 0

而 correlation 方面: cor(x,y) 又等於 cor(x_norm, y_norm),

所以 sd(y)/sd(x) ≠ 1 的情況下, 會導致 slope0 ≠ slope2.

方法 1 是強制通過原點, 而原本最佳解可能是有截距的, 所以 slope0 也不太容易巧合和 slope1 、slope2 一樣.

上面講的還沒有發揮到 R 的精神, 我們用 lm() 重做一次.

x <- c(1.2, 2.8, 3.5, 1.9, 4.1, 2.3, 3.8, 1.6, 2.9, 3.2)
y <- c(2.4, 5.1, 6.8, 3.2, 7.9, 4.1, 7.2, 2.8, 5.5, 6.1)

# 標準化
x_norm <- (x - mean(x)) / sd(x)
y_norm <- (y - mean(y)) / sd(y)

# === 手動計算(原始方法) ===
slope0_manual <- cor(x, y) * sd(y) / sd(x)
slope1_manual <- sum(x * y) / sum(x^2)
slope2_manual <- cor(x_norm, y_norm) * sd(y_norm) / sd(x_norm)

# === 使用 lm() 實現 ===
# 方法0:一般線性迴歸(有截距)
model0 <- lm(y ~ x)
slope0_lm <- coef(model0)[2]  # 取斜率係數

# 方法1:通過原點迴歸(無截距)
model1 <- lm(y ~ x - 1)  # -1 表示移除截距項
slope1_lm <- coef(model1)[1]  # 只有一個係數

# 方法2:標準化後的一般迴歸
model2 <- lm(y_norm ~ x_norm)
slope2_lm <- coef(model2)[2]  # 取斜率係數

# === 比較結果 ===
cat("=== 手動計算 vs lm() 比較 ===\n")
cat("方法0 - 有截距迴歸:\n")
cat("  手動計算:", slope0_manual, "\n")
cat("  lm() 結果:", slope0_lm, "\n")
cat("  差異:", abs(slope0_manual - slope0_lm), "\n\n")

cat("方法1 - 通過原點迴歸:\n")
cat("  手動計算:", slope1_manual, "\n")
cat("  lm() 結果:", slope1_lm, "\n")
cat("  差異:", abs(slope1_manual - slope1_lm), "\n\n")

cat("方法2 - 標準化後迴歸:\n")
cat("  手動計算:", slope2_manual, "\n")
cat("  lm() 結果:", slope2_lm, "\n")
cat("  差異:", abs(slope2_manual - slope2_lm), "\n\n")

# === 顯示完整模型資訊 ===
cat("=== 完整模型摘要 ===\n")
cat("\n--- 方法0:一般線性迴歸 ---\n")
print(summary(model0))

cat("\n--- 方法1:通過原點迴歸 ---\n")
print(summary(model1))

cat("\n--- 方法2:標準化後迴歸 ---\n")
print(summary(model2))

我們可以看到 model1 <- lm(y ~ x – 1) , 它不是真的減 1, -1 表示移除截距項. 還可以表示為 y ~ 0 + x. 兩者等效.

再來 slope2_lm <- coef(model2)[2] 的 [2] 不是第三個係數. 其中, 第一個係數 [1] 就固定是截距, 第二個係數 [2] 就固定是斜率.

Rrrrrrrrrrrrrrrr 啊啊啊啊啊啊啊啊啊啊….. (聲音漸尖)