在R中为h2o模型打印“漂亮”的表格

6

有多个R的软件包可用于从统计模型输出中打印“漂亮”的表格(LaTeX/HTML/TEXT),并轻松比较替代模型规范的结果。

其中一些软件包是apsrtablextablememisctexregoutregstargazer(例如,请参见这里:https://www.r-statistics.com/2013/01/stargazer-package-for-beautiful-latex-tables-from-r-statistical-models-output/)。

是否有任何可与h2o软件包的模型相配的可比较的R软件包?

以下是两个简单的h2o广义线性模型的示例,我希望将它们作为“漂亮”的表并排打印出来。

# Load package and setup h2o
library(h2o)
localH2O <- h2o.init(ip = 'localhost', port = 54321, max_mem_size = '4g')

# Load data
prostatePath <- system.file("extdata", "prostate.csv", package = "h2o")
prostate.hex <- h2o.importFile(path = prostatePath, destination_frame = "prostate.hex")

# Run GLMs
model.output.1 <- h2o.glm(y = "CAPSULE", x = c("RACE","PSA","DCAPS"),
  training_frame = prostate.hex,family = "binomial", nfolds = 0, 
  alpha = 0.5, lambda_search = FALSE)
model.output.2 <- h2o.glm(y = "CAPSULE", x = c("AGE","RACE","PSA","DCAPS"), 
  training_frame = prostate.hex, family = "binomial", nfolds = 0, 
  alpha = 0.5, lambda_search = FALSE)

这是使用常规GLM对象并使用texreg包中的screenreg()函数呈现的样子:
library(data.table)
library(texreg)
d <- fread(prostatePath)
model.output.1.glm <- glm(CAPSULE ~ RACE + PSA + DCAPS, data=d)
model.output.2.glm <- glm(CAPSULE ~ AGE + RACE + PSA + DCAPS, data=d)
screenreg(list(model.output.1.glm, model.output.2.glm))

enter image description here


添加一个到列表中:broom - Keith Hughitt
“beautiful”和“stargazer”是互斥的。这是一个非常糟糕的包 - 不要使用它。我建议改用‹pander›。 - Konrad Rudolph
@Keith Hughitt:broom 中有没有一种函数可以将模型并排比较的功能? - majom
1
我认为通常的术语是“漂亮”,而技术术语是“出版就绪”。 - smci
同意。我使用“美丽”这个词是因为我在我的问题中链接了一篇博客文章。 - majom
3个回答

23
texreg是一个包,作者在此。基于通用函数,它意味着任何用户都可以添加自定义extract方法来适用于任意模型,并使它们与texreg配合使用。下面我将为您介绍这一过程。我希望这篇详细的论述将帮助曾经提出类似问题的其他人设计他们自己的texreg扩展。
另请参见统计软件杂志2013年论文中的第6节,其中还有另一个例子。但首先,我将描述texreg体系结构的更一般工作原理,以便让您了解可能的情况。 texreghtmlregscreenreg三个函数用于将某些经过清理的信息(系数、标准误差、置信区间、p值、拟合优度统计量、模型标签等)转换成相应的输出格式。这并不完美,但我认为它现在已经具有相当多的自定义参数,包括booktabsdcolumn支持。因此,更大的挑战是首先获得经过清理的模型信息。
为此,需要将texreg对象提供给这三个函数之一。一个texreg对象只是一个容器,用于包含系数等信息,并且使用S4类进行正式定义。要创建一个texreg对象,可以使用构造函数createTexreg(如帮助页面中所述),它接受所有不同的信息作为参数,例如标准误差等。或者(更好地),您可以使用extract函数从某个估计模型中提取这些信息,并返回一个texreg对象以供任何这三个函数之一使用。通常的做法是将多个模型的列表直接传递给texregscreenreg等函数,这个函数会内部调用extract来创建texreg对象,然后处理这些对象中的信息。
但同样有效的方法是仅保存调用extract函数的输出结果到一个对象中,可能操纵这个texreg对象,然后在操作后将texreg函数调用该对象以显示它作为一个表格。这允许在调整结果方面有一定的灵活性。

之前提到过,该包使用通用函数。这意味着extract函数是一个通用的函数,你可以为它注册适用于任意类别模型的方法。例如,如果extract函数不知道如何处理h2o对象并从中提取相关信息,你可以编写相应的方法并将其注册到extract函数中。下面,我会逐步向你展示这个过程,希望人们能够从这个详细的解释中学习并开始编写自己的扩展。(注意:如果有人开发出有用的方法,请发送电子邮件给我,我可以在下一个texreg发布版中加入它。)值得一提的是,该包的源文件中包含了超过70个extract方法的示例,你可以将它们用作模板。这些示例存储在文件R/extract.R中。

确定类别标签并设置extract方法

第一步是确定对象的类别名称。在你的例子中,class(model.output.1)返回以下类别标签:“H2OBinomialModel”和“h2o”。第一个标签是更具体的标签,第二个标签是更一般的标签。如果所有的h2o模型对象都以相似的方式结构化,编写一个h2o对象的扩展方法,然后在方法内部决定如何处理特定的模型会是个不错的选择。由于我几乎不了解h2o包,所以我更倾向于在这种情况下为H2OBinomialModel对象编写更具体的extract方法。如果需要,稍后可以对其进行调整。

extract方法的结构如下:你编写一个名为extract.xyz的函数(将“xyz”替换为类别标签),至少有一个参数叫做model,用于接受模型对象(例如,在你的例子中是model.output.1),在函数体中放入一些代码来从model对象中提取相关信息,使用createTexreg构造函数创建一个texreg对象,然后返回此对象。以下是一个空容器:

extract.H2OBinomialModel <- function(model, ...) {
  s <- summary(model)

  # extract information from model and summary object here

  # then create and return a texreg object (replace NULL with actual values):
  tr <- createTexreg(
    coef.names = NULL,    # character vector of coefficient labels
    coef = NULL,          # numeric vector with coefficients
    se = NULL,            # numeric vector with standard error values
    pvalues = NULL,       # numeric vector with p-values
    gof.names = NULL,     # character vector with goodness-of-fit labels
    gof = NULL,           # numeric vector of goodness-of-fit statistics
    gof.decimal = NULL    # logical vector: GOF statistic has decimal points?
  )
  return(tr)
}

注意,函数定义中还包含...参数,可用于自定义在extract方法内进行函数调用时需要传递的参数。

还要注意,函数定义体中的第一行将模型摘要保存在名为s的对象中。这通常很有用,因为许多软件包编写者决定将一些信息存储在简化版本的摘要中,因此通常应该将模型及其摘要视为有用的信息来源。在某些情况下,可能需要查看相应软件包中摘要方法的实际定义,以了解在调用summary命令时显示在摘要页面上的信息片段是如何计算的,因为并不是所有summary方法都存储在summary对象中显示的不同元素。

H2OBinomialModel对象中查找正确的信息

下一步是检查对象并找到所有应显示在最终表格中的详细信息。通过查看model.output.1的输出,我猜测下面的部分应该构成表格底部的GOF块:

MSE:  0.202947
R^2:  0.1562137
LogLoss:  0.5920097
Mean Per-Class Error:  0.3612191
AUC:  0.7185655
Gini:  0.4371311
Null Deviance:  512.2888
Residual Deviance:  449.9274
AIC:  457.9274

接下来的部分可能应该构成表格中间的系数块:

Coefficients: glm coefficients
      names coefficients standardized_coefficients
1 Intercept    -1.835223                 -0.336428
2      RACE    -0.625222                 -0.193052
3     DCAPS     1.314428                  0.408336
4       PSA     0.046861                  0.937107

在许多情况下,摘要包含相关信息,但打印模型会产生我们需要的内容。我们需要将所有这些定位到model.output.1对象中(或其摘要,如果适用)。为此,有几个有用的命令,其中包括str(model.output.1)names(summary(model.output.1))和类似的命令。

让我们从系数块开始。调用str(model)会显示S4对象中的一个名为model的插槽。我们可以通过调用model.output.1@model来查看它的内容。结果是一个带有多个命名元素的列表,其中包括coefficients_table。因此,我们可以通过调用model.output.1@model$coefficients_table来访问系数表。结果是一个数据框,其列可以使用$运算符来访问。特别地,我们需要名称和系数。这里有两种类型的系数,标准化和非标准化,我们可以稍后添加一个参数到提取方法中,以让用户决定他或她想要什么。以下是我们如何提取系数及其标签:

coefnames <- model.output.1@model$coefficients_table$names
coefs <- model.output.1@model$coefficients_table$coefficients
coefs.std <- model.output.1@model$coefficients_table$standardized_coefficients
据我所见,该对象中没有存储标准误差或p值。如果需要,我们可以编写一些额外的代码来计算它们,但在这里,我们将专注于作为模型输出的 readily provided 的内容。
重要的是,我们不应该覆盖任何现有的函数名称,如names或coef,在R中。虽然从技术上讲,这样做应该可以工作,因为代码稍后在函数内执行,但这很容易在尝试事物时导致混淆,所以最好避免这样做。
接下来,我们需要定位拟合优度统计信息。通过仔细检查model.output.1的输出,我们看到拟合优度统计信息包含在model@model$training_metrics@metrics的多个插槽中。让我们将它们保存到一些更易于访问的对象中:
mse <- model.output.1@model$training_metrics@metrics$MSE
r2 <- model.output.1@model$training_metrics@metrics$r2
logloss <- model.output.1@model$training_metrics@metrics$logloss
mpce <- model.output.1@model$training_metrics@metrics$mean_per_class_error
auc <- model.output.1@model$training_metrics@metrics$AUC
gini <- model.output.1@model$training_metrics@metrics$Gini
nulldev <- model.output.1@model$training_metrics@metrics$null_deviance
resdev <- model.output.1@model$training_metrics@metrics$residual_deviance
aic <- model.output.1@model$training_metrics@metrics$AIC

在某些情况下,但不在此处,软件包的作者编写了通用函数的方法,可以用来提取一些共同的信息,例如观测次数(nobs(model)),AIC(AIC(model)),BIC(BIC(model)),偏差(deviance(model))或对数似然度(logLik(model)[[1]])。所以这些是您可能想首先尝试的内容;但是 h2o 软件包似乎没有提供这样的便捷方法。

将信息添加到 extract.H2OBinomialModel 函数中

现在我们已经找到了所有需要的信息,我们可以将它们添加到我们上面定义的 extract.H2OBinomialModel 函数中。

但是,我们希望让用户决定他或她喜欢原始系数还是标准化系数,并且我们希望让用户决定应报告哪些适合度统计数据,因此我们将各种逻辑参数添加到函数头中,然后在函数内部使用 if-conditions 检查我们是否应该将相应的统计数据嵌入到生成的 texreg 对象中。

在这种情况下,我们还删除了 s <- summary(model) 这一行,因为我们实际上不需要汇总中的任何信息,因为我们在模型对象中找到了所有需要的内容。

完整的函数可能看起来像这样:

# extension for H2OBinomialModel objects (h2o package)
extract.H2OBinomialModel <- function(model, standardized = FALSE, 
      include.mse = TRUE, include.rsquared = TRUE, include.logloss = TRUE, 
      include.meanerror = TRUE, include.auc = TRUE, include.gini = TRUE, 
      include.deviance = TRUE, include.aic = TRUE, ...) {

  # extract coefficient table from model:
  coefnames <- model@model$coefficients_table$names
  if (standardized == TRUE) {
    coefs <- model@model$coefficients_table$standardized_coefficients
  } else {
    coefs <- model@model$coefficients_table$coefficients
  }

  # create empty GOF vectors and subsequently add GOF statistics from model:
  gof <- numeric()
  gof.names <- character()
  gof.decimal <- logical()
  if (include.mse == TRUE) {
    mse <- model@model$training_metrics@metrics$MSE
    gof <- c(gof, mse)
    gof.names <- c(gof.names, "MSE")
    gof.decimal <- c(gof.decimal, TRUE)
  }
  if (include.rsquared == TRUE) {
    r2 <- model@model$training_metrics@metrics$r2
    gof <- c(gof, r2)
    gof.names <- c(gof.names, "R^2")
    gof.decimal <- c(gof.decimal, TRUE)
  }
  if (include.logloss == TRUE) {
    logloss <- model@model$training_metrics@metrics$logloss
    gof <- c(gof, logloss)
    gof.names <- c(gof.names, "LogLoss")
    gof.decimal <- c(gof.decimal, TRUE)
  }
  if (include.meanerror == TRUE) {
    mpce <- model@model$training_metrics@metrics$mean_per_class_error
    gof <- c(gof, mpce)
    gof.names <- c(gof.names, "Mean Per-Class Error")
    gof.decimal <- c(gof.decimal, TRUE)
  }
  if (include.auc == TRUE) {
    auc <- model@model$training_metrics@metrics$AUC
    gof <- c(gof, auc)
    gof.names <- c(gof.names, "AUC")
    gof.decimal <- c(gof.decimal, TRUE)
  }
  if (include.gini == TRUE) {
    gini <- model@model$training_metrics@metrics$Gini
    gof <- c(gof, gini)
    gof.names <- c(gof.names, "Gini")
    gof.decimal <- c(gof.decimal, TRUE)
  }
  if (include.deviance == TRUE) {
    nulldev <- model@model$training_metrics@metrics$null_deviance
    resdev <- model@model$training_metrics@metrics$residual_deviance
    gof <- c(gof, nulldev, resdev)
    gof.names <- c(gof.names, "Null Deviance", "Residual Deviance")
    gof.decimal <- c(gof.decimal, TRUE, TRUE)
  }
  if (include.aic == TRUE) {
    aic <- model@model$training_metrics@metrics$AIC
    gof <- c(gof, aic)
    gof.names <- c(gof.names, "AIC")
    gof.decimal <- c(gof.decimal, TRUE)
  }

  # create texreg object:
  tr <- createTexreg(
    coef.names = coefnames, 
    coef = coefs, 
    gof.names = gof.names, 
    gof = gof, 
    gof.decimal = gof.decimal
  )
  return(tr)
}

对于拟合优度块,您可以看到我首先创建了空向量,然后根据用户使用的相应参数打开相应的统计信息来填充它们。

gof.decimal 逻辑向量指示每个 GOF 统计量是否具有小数位 (TRUE) 或不具有小数位 (FALSE,例如观测次数)。

最后,新函数需要注册为通用 extract 函数的方法。这可通过简单命令完成:

setMethod("extract", signature = className("H2OBinomialModel", "h2o"), 
definition = extract.H2OBinomialModel)

这里,className 的第一个参数是类标签,第二个参数是定义该类的包名。

总结一下,编写自定义扩展只需要两件事:1)编写提取方法,2)注册该方法。也就是说,这段代码可以在运行时执行,不必插入任何包中。

但是,为了您的方便,我已经将 H2OBinomialModel 方法添加到 texreg 版本 1.36.13 中,并且可以在 CRAN 上获得。

请注意,本文介绍的解决方案无法与任何先前版本的 texreg 一起使用,因为早期版本无法处理既没有标准误差也没有置信区间的模型。在我看来,这是一个相当专业化的设置,我还没有遇到过仅提供估计值而没有任何不确定度量的包。我已经在 texreg 中修复了这个问题。

尝试新的 extract 方法

一旦函数定义和 setMethod 命令在运行时被执行,以下命令可以用于创建表格:

screenreg(list(model.output.1, model.output.2), custom.note = "")

这是输出结果:

======================================
                      Model 1  Model 2
--------------------------------------
Intercept              -1.84    -1.11 
RACE                   -0.63    -0.62 
DCAPS                   1.31     1.31 
PSA                     0.05     0.05 
AGE                             -0.01 
--------------------------------------
MSE                     0.20     0.20 
R^2                     0.16     0.16 
LogLoss                 0.59     0.59 
Mean Per-Class Error    0.36     0.38 
AUC                     0.72     0.72 
Gini                    0.44     0.44 
Null Deviance         512.29   512.29 
Residual Deviance     449.93   449.51 
AIC                   457.93   459.51 
======================================

custom.note = ""参数在这里是有意义的,因为我们不想要重要性图例,因为模型没有报告任何不确定性度量。

还可以抑制一些GOF度量和/或使用标准化系数:

screenreg(list(model.output.1, model.output.2), custom.note = "", 
    include.deviance = FALSE, include.auc = FALSE, standardized = TRUE)

结果:

======================================
                      Model 1  Model 2
--------------------------------------
Intercept              -0.34    -0.34 
RACE                   -0.19    -0.19 
DCAPS                   0.41     0.41 
PSA                     0.94     0.94 
AGE                             -0.07 
--------------------------------------
MSE                     0.20     0.20 
R^2                     0.16     0.16 
LogLoss                 0.59     0.59 
Mean Per-Class Error    0.36     0.38 
Gini                    0.44     0.44 
AIC                   457.93   459.51 
======================================

可与createTexreg一起使用的其他插槽

createTexreg构造函数在任何extract方法中被调用。上面的示例显示了一些简单的信息。除了示例中包含的细节外,texreg对象中还提供了以下插槽:

  • se:标准误差
  • pvalues:p值
  • ci.low:置信区间下界
  • ci.up:置信区间上界
  • model.name:当前模型的标题

请注意,应仅使用置信区间或标准误差和p值之一。

操作texreg对象

除了直接将模型交给screenregtexreghtmlreg函数外,还可以先将提取的信息保存到texreg对象中并在显示或保存表格之前对其进行操作。这样做的附加价值在于,即使对表格进行复杂的更改也很容易。例如,可以重命名系数或GOF统计量、添加新行、重命名模型、修改值或更改系数或GOF统计量的顺序。以下是如何实现此操作的方式:首先,调用extract函数将信息保存到texreg对象中:

tr <- extract(model.output.1)

您可以通过调用 tr 来显示 texreg 对象的内容,它将显示以下输出:

No standard errors and p-values were defined for this texreg object.

                coef.
Intercept -1.83522343
RACE      -0.62522179
DCAPS      1.31442834
PSA        0.04686106

                             GOF dec. places
MSE                    0.2029470        TRUE
R^2                    0.1562137        TRUE
LogLoss                0.5920097        TRUE
Mean Per-Class Error   0.3612191        TRUE
AUC                    0.7185655        TRUE
Gini                   0.4371311        TRUE
Null Deviance        512.2888402        TRUE
Residual Deviance    449.9273825        TRUE
AIC                  457.9273825        TRUE

或者,这是对象的结构,如str(tr)所示:

Formal class 'texreg' [package "texreg"] with 10 slots
  ..@ coef.names : chr [1:4] "Intercept" "RACE" "DCAPS" "PSA"
  ..@ coef       : num [1:4] -1.8352 -0.6252 1.3144 0.0469
  ..@ se         : num(0) 
  ..@ pvalues    : num(0) 
  ..@ ci.low     : num(0) 
  ..@ ci.up      : num(0) 
  ..@ gof.names  : chr [1:9] "MSE" "R^2" "LogLoss" "Mean Per-Class Error" ...
  ..@ gof        : num [1:9] 0.203 0.156 0.592 0.361 0.719 ...
  ..@ gof.decimal: logi [1:9] TRUE TRUE TRUE TRUE TRUE TRUE ...
  ..@ model.name : chr(0) 
现在您可以以任意方式操作此对象,例如添加一个GOF统计数据:
tr@gof.names <- c(tr@gof.names, "new statistic")
tr@gof <- c(tr@gof, 12)
tr@gof.decimal <- c(tr@gof.decimal, FALSE)

或者您可以改变系数的顺序:

tr@coef.names <- tr@coef.names[c(4, 1, 2, 3)]
tr@coef <- tr@coef[c(4, 1, 2, 3)]

完成操作后,您可以在调用 screenreg 等函数时,将 texreg 对象交出,而不是原始模型:

screenreg(list(tr, model.output.2), custom.note = "")

新结果将如下所示:

======================================
                      Model 1  Model 2
--------------------------------------
PSA                     0.05     0.05 
Intercept              -1.84    -1.11 
RACE                   -0.63    -0.62 
DCAPS                   1.31     1.31 
AGE                             -0.01 
--------------------------------------
MSE                     0.20     0.20 
R^2                     0.16     0.16 
LogLoss                 0.59     0.59 
Mean Per-Class Error    0.36     0.38 
AUC                     0.72     0.72 
Gini                    0.44     0.44 
Null Deviance         512.29   512.29 
Residual Deviance     449.93   449.51 
AIC                   457.93   459.51 
new statistic          12             
======================================

简述

texreg可以被用户自定义。只需编写像上面展示的提取方法,并使用setMethods调用进行注册即可。我已经将H2OBinomialModel方法与修复使用没有标准误差模型(如此模型)的错误一起包含在最新的texreg版本 1.36.13中。


2
让我给你喊个好。解释得真棒! - majom

1
你可以使用R xtable包与h2o的H2OTable一起使用(或者如果您使用as.h2o(your_H2OTable)将H2OTable转换为H2OFrame,也可以使用knitr),如果您从模型输出中提取它们。
例如,要从模型系数创建漂亮的表格,您需要首先提取系数表格,使用model.output.1@model$coefficients_table,然后可以使用xtable:xtable(prostate.glm@model$coefficients_table)打印出Latex代码。
对于并排视图,有多篇文章介绍如何在knitr或xtablextable and sweave中执行此操作。

这指向了正确的方向。然而,至少在链接的帖子中展示的并排表格并不一定匹配相应的系数。它们只是简单地把两个表格并排放置,对吗? - majom

0

目前还没有一个能够做到这一点的软件包。broom 软件包尚不支持 H2O 模型,如果支持的话那就太棒了!也许将来会有这样的功能。一旦有了一种使用 broom 或类似功能将模型输出“整理”成 R 数据框的方法,那么 xtable 等工具将能够很好地发挥作用。


这似乎还不是一个答案。 - Gregor Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接