如何按模型列表中的出现顺序对系数名称的特征向量重新排序

How to reorder a character vector of coefficient names by order of appearance in a list of models

R 中的 stargazer package 非常适合将多元回归模型显示为并排列——许多社会科学学科的标准样式。但是,该包不能很好地与 knitr+pandoc 一起使用,因为它生成的输出为 HTML 或 TeX,但不是 Markdown。

作为解决方案,我正在创建一个函数,该函数可以生成类似于使用 stargazer 创建的表格,但将输出保存为一个简单的数据框,然后我可以使用 kable 等包进行渲染和针织文档中的 pander。对于 broom::tidy.

这样的函数,这样做很简单

但是,我对如何对模型中显示的系数进行排序感到困惑。以这三个模型为例。用stargazer显示时,最终的系数阶数为c("wt", "qsec", "hp", "cyl", "gear", "carb", "drat")。所有系数的顺序主要基于第一个模型中的系数(wtqseccylgearcarb)。当第二个模型作为新列附加时,hp 行插入到 qsec 之后和 cyl.

之前
lm0 <- lm(hp ~ wt + qsec + cyl + gear + carb, mtcars)
lm1 <- lm(qsec ~ hp + cyl + gear + carb, mtcars)
lm2 <- lm(qsec ~ wt + hp + gear + drat, mtcars)

stargazer(lm0, lm1, lm2, type="text")
====================================================
              (1)           (2)             (3)          
----------------------------------------------------
wt           16.879                       0.827**        
            (12.113)                      (0.383)        
qsec         -8.124                                      
            (6.109)                                      
hp                         -0.005        -0.026***       
                          (0.007)         (0.004)        
cyl         18.210**     -0.811***                       
            (8.785)       (0.280)                        
gear         13.342      -1.597***         -0.232        
            (15.115)      (0.441)         (0.439)        
carb         9.277         0.098                         
            (6.345)       (0.222)                        
drat                                       0.099         
                                          (0.636)        
Constant     49.424      29.181***       19.530***       
           (171.876)      (2.398)         (2.766)        
====================================================
Note:                    *p<0.1; **p<0.05; ***p<0.01

最后,我希望生成一个系数名称的字符向量,然后我可以将其与 dplyr::arrange() 一起使用以正确排序多个模型系数的数据框。

排序似乎遵循这个伪算法:

  1. 保存系数名称的第一个列表 (list_1)
  2. 浏览第二个名单。如果 list_2element_1 不匹配 list_1element_1,检查 list_1 的下一个元素直到匹配,然后在匹配之前插入
  3. list_2element_2 放在 element_1 之后,如果它不匹配 list_1
  4. 中的任何其他内容
  5. 重复list_3,依此类推

然而,事实证明,编写简单的 R 代码来生成此订单比我想象的要困难。简单地将所有系数名称连接到一个向量中,然后仅保留唯一值并不能产生正确的顺序,因为新变量(如 hp)只是添加到现有变量名称的末尾,而不是插入到中间:

library(tidyverse)
names1 <- names(lm0$coefficients) %>% discard(~ .x == "(Intercept)")
names2 <- names(lm1$coefficients) %>% discard(~ .x == "(Intercept)")
names3 <- names(lm2$coefficients) %>% discard(~ .x == "(Intercept)")

# New variables just appended
unique(c(names1, names2, names3))

# [1] "wt"   "qsec" "cyl"  "gear" "carb" "hp"   "drat"

此外,似乎实现这样的事情的唯一方法是使用大量循环,感觉效率极低。

那么,最后,我如何根据模型列表中出现的顺序对系数名称的特征向量进行排序或重新排序,优先考虑列表中第一个模型的顺序?也就是说,最终这是我想要得到的字符向量:c("wt", "qsec", "hp", "cyl", "gear", "carb", "drat")


更新:memisc::mtable(lm0, lm1, lm2) 是一个 neat alternative to stargazer,实际上 returns 一个数据框(而不仅仅是文本),但它不会在现有顺序中插入新系数,并且而是将它们附加到列表中(最后是 hpdrat)。它似乎只是连接所有系数名称并使用它们的唯一值。

===================================================
                     lm0        lm1        lm2     
---------------------------------------------------
  (Intercept)       49.424   29.181***  19.530***  
                  (171.876)  (2.398)    (2.766)    
  wt                16.879               0.827*    
                   (12.113)             (0.383)    
  qsec              -8.124                         
                    (6.109)                        
  cyl               18.210*  -0.811**              
                    (8.785)  (0.280)               
  gear              13.342   -1.597**   -0.232     
                   (15.115)  (0.441)    (0.439)    
  carb               9.277    0.098                
                    (6.345)  (0.222)               
  hp                         -0.005     -0.026***  
                             (0.007)    (0.004)    
  drat                                   0.099     
                                        (0.636)    
---------------------------------------------------

回答OP的问题

So, in the end, how can I sort or reorder a character vector of coefficient names by order of appearance in a list of models, prioritizing the order of the first model in the list?

这里有一个 one-liner 应该适用于任意数量的模型:

unique(names(unlist(lapply(list(lm0, lm1, lm2), coef))))[-1]
#[1] "wt"   "qsec" "cyl"  "gear" "carb" "hp"   "drat"

请注意,代码隐含假设第一个模型始终具有 "(Intercept)" 作为第一个系数,该系数通过负索引 [-1] 从结果向量中删除。

如果不能保证这一点,使用它可能更安全

setdiff(unique(names(unlist(lapply(list(lm0, lm1, lm2), coef)))), "(Intercept)")

从结果向量中删除 "(Intercept)"(如果有)且无论其位置如何。其余系数名称将保持其顺序:

#[1] "wt"   "qsec" "cyl"  "gear" "carb" "hp"   "drat"

编辑

不太清楚 stargazer 实现了哪个逻辑来对系数进行排序。但是,根据帮助页面,stargazer 也 return 与字符向量一样无形地输出。 此外, table.layout 参数可以仅用于 return 系数部分。这可用于按照与 stargazer:

相同的顺序提取系数的名称
sgt <- capture.output(stargazer::stargazer(lm0, lm1, lm2, type="text", table.layout = "t"))
setdiff(stringr::str_extract(sgt, "^\w*"), c("", "Constant"))
#[1] "wt"   "qsec" "hp"   "cyl"  "gear" "carb" "drat"

由于stargazer使用cat()输出,capture.output()保持控制台输出干净(感谢@Andrew的建议)。

正则表达式str_extract()return是每个字符串开头的第一个"word"。使用 setdiff().

再次清理结果向量