理解为什么线性回归没有按预期处理我的分类变量?

Understanding why linear regression isn't treating my categorical variable as expected?

我正在阅读有关公式和线性回归的内容,但我无法理解如何解释 lm 的输出以用于具有多个参数和分类变量的线性回归。

我想我了解如何解释简单 y = 的输出 a + bx 公式(如果我下面说的有误,请纠正我)。

#library(tidyverse)
#library(modelr)
require(ggplot2)
require(dplyr)

diamonds2 <- diamonds %>%
  mutate(lprice = log2(price), lcarat = log2(carat))

mod <- lm(
  lprice ~ lcarat,
  data = diamonds2
)

#diamonds2 %>% modelr::add_predictions(mod, "pred_price")
diamonds2$pred_price <- predict(mod, diamonds2) # if you don't have modelr

模型(mod)是

Call:
lm(formula = lprice ~ lcarat, data = diamonds2)

Coefficients:
(Intercept)       lcarat  
     12.189        1.676  

据我了解,这意味着当我添加预测时,生成预测的公式是

pred_price = 12.189 + (1.676 * lcarat)

我在公式中添加分类变量时感到困惑

diamonds2 <- diamonds %>%
  mutate(lprice = log2(price), lcarat = log2(carat))

mod <- lm(
  lprice ~ lcarat + cut,   # I added a categorical variable here
  data = diamonds2
)

diamonds2 %>%
  add_predictions(mod, "pred_price")

现在模型是

Call:
lm(formula = lprice ~ lcarat + cut, data = diamonds2)

Coefficients:
(Intercept)       lcarat        cut.L        cut.Q        cut.C        cut^4  
   12.10711      1.69577      0.32364     -0.09583      0.07631      0.02688  

我对一些事情感到困惑。

1) diamonds$cut 有五个可能的值(fair、good、very good、premium、ideal),那么为什么模型只显示 cut 的四个值?

2) 据我了解,R 在线性回归方程中将分类变量视为 1 或 0,因此在评估数据行时每个 "cut" 系数将乘以 1 或 0。对吗?

3) 如何根据上面给出的系数写出 y = a_0 + (a_1 * x_1) + (a_2 * x_2)...?在这种情况下可以吗?

1) lm 做了一些不想要的事情,它 将 diamonds$cut 变量视为有序分类而不是分类(即没有使用通常的 1/0 虚拟变量对比处理).

最初我认为您只需要通过编写 lm(formula = lprice ~ lcarat + factor(cut)) 或修复数据框 diamonds2$cut <- factor(diamonds2$cut).

来确保 lm 获得分类

您期望看到分类 cut 的对比度级别。 (5 级分类会给出 4 个对比(和一个截距);请参阅帮助文档(对比)。但是你没有从 lm 获得对比级别,你得到了多项式系数。

深入研究为什么会发生这种情况,我们注意到 str(cut) 告诉我们 cut 是一个 有序 分类 (这是罪魁祸首):

> str(diamonds$cut)
 Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...

为了进一步了解 lm 这样做的原因,我查看了 help(contrast), help(lm) and help(model.matrix.default) 的页面,这让我找到了 options('contrasts'):

> getOption('contrasts')
        unordered           ordered 
"contr.treatment"      "contr.poly" 

这意味着 lm 在 有序 分类(如 diamonds$cut)上生成对比的默认行为是不需要的 contr.poly()。因此,要么更改默认值,要么将 cut 转换为无序分类,或者创建一个新的 var diamonds$cut <- factor(diamonds$cut, ordered=F) 两种解决方案的代码都在底部。

2) 不,如果您将 diamonds$cut 作为分类传递,就会发生这种情况。但是,您得到了 cut.L, .Q, .C, ^4...,它们是 cut 值中(不需要的)多项式的线性、二次、三次、四次系数,它试图拟合 lcarat 的观察值(请参阅 contrasts 上的帮助正在调用 contr.poly 并使用 n=4 个级别)。这不是你想要的。

另一个线索是那些级别应该被命名 cut.Fair, cut.Good, cut.Very_Good, cut.Premium, cut.Ideal(你会从这 5 个中得到 4 个;另一个将被删除)。

3) 修复它以将 cut 视为(无序)因子后,您应该得到: lprice = coeff.price * lcarat + coeff.Fair * cut.Fair + coeff.Good * cut.Good + ... + coeff.Ideal * cut.Ideal

FIX/WORKAROUND 1:

# Save the old ordered-categorical, then clobber it with the unordered one so that lm() Does The Right Thing (tm)
diamonds$cut.ordered <- diamonds$cut
diamonds$cut <- factor(diamonds$cut, ordered=F)

OR ELSE FIX 2:
# Make lm treat all categoricals as unordered categoricals, even ordered ones
#options('contrasts' = c('contr.treatment','contr.treatment') )

lm(log(price) ~ log(carat) + cut, data=diamonds)

Coefficients:
 (Intercept)    log(carat)       cutGood  cutVery Good    cutPremium  
      8.2001        1.6958        0.1632        0.2408        0.2382  
    cutIdeal  
      0.3172  

TL;DR

diamonds$cut has five possible values (fair, good, very good, premium, ideal), so why does the model only show four values for cut?

因子通常用比因子水平少一个的系数表示。那是因为您还在估计模型的截距。您本应在因子的另一个级别获得的信息在截距中表示。

From my understanding, R treats a categorical variable as being either 1 or 0 in the linear regression equation, so each "cut" coefficient will either be multiplied by 1 or 0 when evaluating a data row. Is that correct?

情况并非总是如此。这对于传统的虚拟编码 (contr.treatment) 来说是正确的,但是还有很多其他方法可以将因子输入到模型中。在您提供的模型中,您有 orthogonal polynomial contrast codes.

3) How do I write a y = a_0 + (a_1 * x_1) + (a_2 * x_2)... from that coefficients given above? Is that possible in this case?

不是不可能,而是比较难(详见下文)。多项式对比变量不能总是在单组比较中整齐地表示,因为它们代表了水平上的总体趋势,因此它们更难根据回归方程来考虑。一个合适的近似值是:

lprice = 12.10711 + 1.69577*lcarat + 0.32364*Lin_cut + -0.09583*Qua_cut + 0.07631*Cub_cut + 0.02688*4_cut + error

其中Lin_cut是cut的线性趋势,Qua_cut是cut的二次趋势,Cub_cut是cut的三次趋势,4_cut是cut的4^ 切割趋势。

更长的解释

cut 是一个有序因子,这意味着它是分类的,但它代表一些潜在的连续变量,因此水平的顺序很重要。请注意 R 描述 cut 与另一个因素的不同之处:

> str(diamonds$cut)
 Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
> str(iris$Species)
 Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

由于有序因子的分析和解释通常与其他因子略有不同,因此在 lm() 模型中输入时,R 的默认值以不同方式对待它们:

> options("contrasts")
$contrasts
        unordered           ordered 
"contr.treatment"      "contr.poly" 

要在 lm() 中输入具有 k 个水平的因子作为预测变量,您需要将其转换为 k-1 个代码。有几种方法可以做到这一点,所有这些都是不错的选择;不同之处在于它们改变了对您从模型中获得的系数的解释,因此根据您要回答的问题类型,您需要选择一种编码分类变量的策略而不是另一种。

contr.treatment

contr.treatment 创建有时称为 "traditional" dummy codes 的内容。一个水平(因子的第一水平,默认情况下)被视为参考组,然后每个代码代表该参考组与其他水平之间的差异。

> lm(Petal.Width ~ Species, data = iris)

Call:
lm(formula = Petal.Width ~ Species, data = iris)

Coefficients:
      (Intercept)  Speciesversicolor   Speciesvirginica  
            0.246              1.080              1.780  
> levels(iris$Species)
[1] "setosa"     "versicolor" "virginica" 

在此示例中,Petal.Width 的平均值在参考组 (setosa) 中为 0.246,在云芝中为 0.246 + 1.080 = 1.36,在弗吉尼亚中为 0.246 + 1.780 = 2.026。

> library(dplyr)
> iris %>% group_by(Species) %>% summarize(Petal.Width = mean(Petal.Width))
# A tibble: 3 × 2
     Species Petal.Width
      <fctr>       <dbl>
1     setosa       0.246
2 versicolor       1.326
3  virginica       2.026

R 会在后台自动为您进行虚拟编码,但您始终可以通过以下方式进行检查:

> mod$contrasts
$Species
[1] "contr.treatment"

这就是那些虚拟编码变量的样子:

> contr.treatment(levels(iris$Species))
           versicolor virginica
setosa              0         0
versicolor          1         0
virginica           0         1

创建了两个虚拟代码(此处为两列),因为因子中有三个水平。对于数据集中物种为 setosa 的每个案例,两个虚拟代码都将为 0。当物种为云芝时,云芝虚拟为 1,而弗吉尼亚虚拟为 0。当物种为弗吉尼亚时,该虚拟为 1,并且其他为 0。

contr.poly

虽然您当然可以用传统的虚拟代码表示任何分类变量,但这并不总是提供最多信息的方法。生成的系数根据参考水平测试每个水平,这可能对您的数据没有任何特别的意义。如果你在 R 中执行 ?contr.treatment,你会看到几个方便的选项,但如果内置代码不能满足你的需要,你也可以从头开始编写自己的代码。

对于有序因子,R 假设多项式趋势对比在大多数情况下最有用,这就是为什么它是默认值。你可以看到它是如何工作的:

> contr.poly(levels(diamonds$cut))
             .L         .Q            .C         ^4
[1,] -0.6324555  0.5345225 -3.162278e-01  0.1195229
[2,] -0.3162278 -0.2672612  6.324555e-01 -0.4780914
[3,]  0.0000000 -0.5345225 -4.095972e-16  0.7171372
[4,]  0.3162278 -0.2672612 -6.324555e-01 -0.4780914
[5,]  0.6324555  0.5345225  3.162278e-01  0.1195229

这些代码不像 contr.treatment 代码那样易于解释,但绘图可能会有所帮助:

library(tidyr)
library(ggplot2)

contr.poly(levels(diamonds$cut)) %>% 
as.data.frame() %>% 
mutate(level=1:nrow(codes)) %>% 
gather("key", "value", -level) %>% 
ggplot(aes(x=level, y=value,color = key)) + 
geom_line()

这样就更清楚了,线性趋势 (L) 的代码形成一条直线,而二次趋势的代码形成 U 形,三次趋势的代码形成一种倾斜的N 形和 ^4 趋势形成一个 U 形,中间有一个尖峰。 contrast codes 可以解释为那些趋势中的每一个,所以 L 代码被解释为数据中的线性趋势,Q 代码是数据中的二次趋势等。

数据中的每个个案都获取这四个对比代码中每一个的值,这些对比代码变量用于估计模型。例如,对于 cut="Fair" 的变量,线性对比代码变量的值为 -0.632,二次值为 0.534,三次值为 -.316,^4 值为 0.119。

对于您的模型,您最终得到了正线性切割趋势(cut.L 的系数为正,并且显着不同于零,您可以通过 运行 summary(mod)).这意味着切割越好,控制对数(克拉),对数(价格)越高:理想的价格高于优质,价格高于非常好,等等。你也看到负二次趋势,虽然,这表明倒U形。这表明中等质量的切割比线性趋势预期的 log(price) 更高——正线性加上负二次。正立方表明 log(price) 从 Good 到 Premium 有一些下降,或者至少比线性和二次趋势预期的增长要少。 ^4 趋势表明 Very Good 切工的对数(价格)相对于 Good 和 Premium 而言高于预期。

进一步阅读

有关多项式趋势对比的更深入的解释,请参阅 this excellent answer on Cross Validated