Imprima tablas "bonitas" para modelos h2o en R
stargazer texreg (3)
Existen múltiples paquetes para
R
que ayudan a imprimir tablas "bonitas" (LaTeX / HTML / TEXT) desde la salida de modelos estadísticos Y para comparar fácilmente los resultados de especificaciones de modelos alternativos.
Algunos de estos paquetes son
apsrtable
,
xtable
,
memisc
,
texreg
,
outreg
y
stargazer
(para ver ejemplos, consulte aquí:
https://www.r-statistics.com/2013/01/stargazer-package-for-beautiful-latex-tables-from-r-statistical-models-output/
).
¿Existe algún paquete
R
comparable que admita los modelos del paquete
h2o
?
Aquí hay un ejemplo de dos modelos GLM simples con
h2o
que me gusta imprimir uno al lado del otro como tablas "hermosas".
# 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)
Así es como se vería con un objeto GLM normal usando
screenreg()
del paquete
texreg
:
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))
No, no hay un paquete que haga esto actualmente. El paquete de broom aún no es compatible con los modelos H2O, ¡eso sería genial! Tal vez eso podría suceder en el futuro. Una vez que haya una manera de "ordenar" la salida del modelo en un marco de datos R usando una escoba o una funcionalidad similar, entonces xtable, etc. funcionará bien.
puede usar el paquete R xtable con H2OTable de h2o (o
knitr
si convierte el H2OTable en un H2OFrame usando
as.h2o(your_H2OTable)
), si los extrae de la salida del modelo.
por ejemplo, para crear una hermosa tabla a partir de los coeficientes de un modelo, primero deberá extraer la tabla de coeficientes con
model.output.1@model$coefficients_table
, luego puede usar xtable:
xtable(prostate.glm@model$coefficients_table)
para imprimir El código de látex.
para vistas de lado a lado, hay múltiples publicaciones sobre cómo hacer esto en knitr o xtable , o xtable y sweave
texreg
autor del paquete
texreg
aquí.
texreg
se basa en funciones genéricas, lo que significa que cualquier usuario puede agregar métodos de
extract
personalizados para modelos arbitrarios y hacer que funcionen con
texreg
.
Te guiaré a través de esto a continuación.
Espero que esta exposición detallada ayude a otras personas que han hecho preguntas similares en el pasado a diseñar sus propias extensiones de
texreg
.
Vea también la Sección 6 en el
artículo de 2013 en el Journal of Statistical Software
para otro ejemplo.
Primero, sin embargo, describiré cómo funciona la arquitectura
texreg
más general para darle una idea de lo que es posible.
texreg
y la función de
extract
genérico
Hay tres funciones:
texreg
(para salida LaTeX),
htmlreg
(para salida HTML, que también puede ser interpretada por Word o Markdown en la mayoría de los escenarios de uso) y
screenreg
(para salida de texto ASCII en la consola).
Estas tres funciones sirven para convertir información limpiada (coeficientes, errores estándar, intervalos de confianza, valores p, estadísticas de bondad de ajuste, etiquetas de modelo, etc.) en los formatos de salida respectivos.
Esto no es perfecto y podría ser aún más flexible, pero creo que tiene bastantes argumentos para la personalización en este punto, incluidas cosas como las
booktabs
y el soporte de
dcolumn
.
Entonces, el gran desafío es, en primer lugar, obtener información del modelo limpiada.
Esto se hace proporcionando un objeto
texreg
a cualquiera de estas tres funciones.
Un objeto
texreg
es solo un contenedor para coeficientes, etc. y se define formalmente usando una clase S4.
Para crear un objeto
texreg
, puede usar la función constructora
createTexreg
(como se documenta en las páginas de ayuda), que acepta todos los diferentes elementos de información como argumentos, como los errores estándar, etc. O (mejor) puede usar el
extract
función para extraer esos datos de algún modelo estimado y devolver un objeto
texreg
para usar con cualquiera de las tres funciones.
La forma en que normalmente hace esto es simplemente entregando una lista de varios modelos a la función
texreg
o
screenreg
etc., y esta función llamará internamente a
extract
para crear objetos
texreg
y luego procesará la información de esos objetos.
Sin embargo, es igualmente válido simplemente guardar la salida de una llamada de la función de
extract
en un objeto, posiblemente manipular este objeto
texreg
y luego llamar a la función
texreg
en el objeto manipulado para mostrarlo como una tabla.
Esto permite cierta flexibilidad para ajustar los resultados.
Anteriormente, mencioné que el paquete usa funciones genéricas.
Esto significa que la función de
extract
es genérica en el sentido de que puede registrar métodos para ella que funcionan con clases arbitrarias de modelos.
Por ejemplo, si la función de
extract
no sabe cómo manejar objetos
h2o
y cómo extraer la información relevante de dicho objeto, simplemente puede escribir un método para hacerlo y registrarlo con la función de
extract
.
A continuación, lo guiaré paso a paso con la esperanza de que la gente aprenda de esta exposición detallada y comience a escribir sus propias extensiones.
(Nota: si alguien desarrolla un método útil, envíeme un correo electrónico y puedo incluirlo en la próxima versión de
texreg
). También vale la pena señalar que los archivos fuente del paquete contienen más de 70 ejemplos de métodos de
extract
que puede puede usar como plantillas.
Estos ejemplos se almacenan en el archivo
R/extract.R
.
Identificar la etiqueta de clase y configurar un método de
extract
El primer paso es identificar el nombre de la clase del objeto.
En su ejemplo, la
class(model.output.1)
devuelve las siguientes etiquetas de clase: "H2OBinomialModel" y "h2o".
La primera etiqueta es la más específica, y la segunda etiqueta es la más general.
Si todos los objetos del modelo
h2o
están estructurados de manera similar, tendrá sentido escribir una extensión para los objetos
h2o
y luego decidir dentro del método cómo proceder con el modelo específico.
Como prácticamente no sé nada sobre el paquete
h2o
, prefiero comenzar con un método de
extract
más específico para los objetos
H2OBinomialModel
en este caso.
Se puede ajustar más adelante si deseamos hacerlo.
extract
métodos de
extract
están estructurados de la siguiente manera: escribe una función llamada
extract.xyz
(reemplace "xyz" por la etiqueta de clase), tiene al menos un argumento llamado
model
, que acepta el objeto modelo (por ejemplo,
model.output.1
en su ejemplo ), coloque un código en el cuerpo que extraiga la información relevante del objeto
model
, cree un objeto
texreg
utilizando el constructor
createTexreg
y devuelva este objeto.
Aquí hay un contenedor vacío:
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)
}
Tenga en cuenta que la definición de la función también contiene el argumento
...
, que se puede utilizar para argumentos personalizados que se deben entregar a las llamadas a funciones dentro del método de
extract
.
Tenga en cuenta también que la primera línea en el cuerpo de la definición de la función guarda el resumen del modelo en un objeto llamado
s
.
Esto suele ser útil porque muchos redactores de paquetes deciden almacenar parte de la información en una versión más simple en el resumen, por lo que generalmente se debe considerar ambos, el modelo y su resumen, como fuentes útiles de información.
En algunos casos, es posible que tenga que mirar la definición real del método de resumen en el paquete respectivo para averiguar cómo se calculan los datos que se muestran en la página de resumen cuando se llama al comando de
summary
porque no todos los métodos de
summary
almacenan los diferentes elementos que se muestran en el objeto de
summary
.
Localizar la información correcta en un objeto
H2OBinomialModel
El siguiente paso es examinar el objeto y localizar todos los detalles que deberían mostrarse en la tabla final.
Al observar la salida de
model.output.1
, supongo que la siguiente parte debería constituir el bloque GOF en la parte inferior de la tabla:
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
Y la siguiente parte probablemente debería constituir el bloque de coeficientes en el medio de la tabla:
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
En muchos casos, el resumen contiene la información relevante, pero aquí imprimir el modelo produce lo que necesitamos.
Necesitaremos ubicar todo esto en el objeto
model.output.1
(o su resumen, si corresponde).
Para hacer eso, hay varios comandos útiles.
Entre ellos se encuentran
str(model.output.1)
,
names(summary(model.output.1))
y comandos similares.
Comencemos con el bloque de coeficientes.
Llamar a
str(model)
revela que hay una ranura llamada
model
en el objeto S4.
Podemos ver su contenido llamando a
model.output.1@model
.
El resultado es una lista con varios elementos con nombre, entre ellos
coefficients_table
.
Entonces podemos acceder a la tabla de coeficientes llamando a
model.output.1@model$coefficients_table
.
El resultado es un marco de datos cuyas columnas podemos acceder utilizando el operador
$
.
En particular, necesitamos los nombres y los coeficientes.
Aquí hay dos tipos de coeficientes, estandarizados y no estandarizados, y podemos agregar un argumento a nuestro método de extracción más adelante para permitir que el usuario decida lo que quiere.
Así es como extraemos los coeficientes y sus etiquetas:
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
Hasta donde puedo ver, no hay errores estándar o valores p almacenados en el objeto. Podríamos escribir un código adicional para calcularlos si quisiéramos hacerlo, pero aquí nos centraremos en las cosas que se proporcionan fácilmente como parte de la salida del modelo.
Es importante que no sobrescribamos ningún nombre de función existente en
R
, como
names
o
coef
.
Si bien esto debería funcionar técnicamente porque el código se ejecuta dentro de una función más adelante, esto puede conducir fácilmente a la confusión al probar cosas, por lo que es mejor evitarlo.
Luego, necesitamos localizar las estadísticas de bondad de ajuste.
Al examinar la salida de
str(model.output.1)
cuidadosamente, vemos que las estadísticas de bondad de ajuste están contenidas en varios espacios bajo
model@model$training_metrics@metrics
.
Vamos a guardarlos en algunos objetos que son más fáciles de acceder:
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
En algunos casos, pero no aquí, el autor de un paquete escribe métodos para funciones genéricas que se pueden usar para extraer información común como el número de observaciones (
nobs(model)
), AIC (
AIC(model)
), BIC (
BIC(model)
), desviación (
deviance(model)
) o la probabilidad de registro (
logLik(model)[[1]]
).
Entonces, estas son cosas que quizás quieras probar primero;
pero el paquete
h2o
no parece ofrecer tales métodos convenientes.
Agregar la información a la función
extract.H2OBinomialModel
Ahora que hemos localizado todos los datos que necesitamos, podemos agregarlos al
extract.H2OBinomialModel
Función
extract.H2OBinomialModel
que definimos anteriormente.
Sin embargo, nos gustaría dejar que el usuario decida si prefiere coeficientes brutos o estandarizados, y nos gustaría que el usuario decida qué estadísticas de bondad de ajuste se deben informar, por lo que agregamos varios argumentos lógicos al encabezado de la función y luego use las condiciones if dentro de la función para verificar si debemos incrustar las estadísticas respectivas en el objeto
texreg
resultante.
También eliminamos la línea
s <- summary(model)
en este caso porque en realidad no necesitamos ningún tipo de información del resumen ya que encontramos todo lo que necesitamos en el objeto del modelo.
La función completa puede verse así:
# 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)
}
Para el bloque de bondad de ajuste, puede ver que primero creé vectores vacíos y luego los rellené con estadísticas adicionales siempre que el usuario activara la estadística respectiva utilizando el argumento respectivo.
El vector lógico
gof.decimal
indica para cada estadística GOF si tiene decimales (
TRUE
) o no (
FALSE
, como en el número de observaciones, por ejemplo).
Finalmente, la nueva función debe registrarse como método para la función de
extract
genérica.
Esto se hace usando un comando simple:
setMethod("extract", signature = className("H2OBinomialModel", "h2o"),
definition = extract.H2OBinomialModel)
Aquí, el primer argumento de
className
es la etiqueta de clase, y el segundo es el paquete en el que se define la clase.
En resumen, las dos únicas cosas que deben hacerse para escribir una extensión personalizada son 1) escribir un método de extracción y 2) registrar el método. Es decir, este código puede ejecutarse en tiempo de ejecución y no tiene que insertarse en ningún paquete.
Sin embargo, para su comodidad, he agregado el método
texreg
versión 1.36.13 de texreg, que está disponible en CRAN.
Tenga en cuenta que la solución presentada aquí no funciona con ninguna versión anterior de
texreg
porque las versiones anteriores no podían tratar con modelos que no tenían errores estándar ni intervalos de confianza.
Esta es una configuración bastante especializada en mi opinión, y no había encontrado un paquete que simplemente proporciona las estimaciones sin ninguna medida de incertidumbre.
Ahora he arreglado esto en
texreg
.
Probar el nuevo método de
extract
Una vez que la definición de la función y el comando
setMethod
se han ejecutado en tiempo de ejecución, se puede usar el siguiente comando para crear una tabla:
screenreg(list(model.output.1, model.output.2), custom.note = "")
Esta es la salida:
======================================
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
======================================
El
custom.note = ""
tiene sentido aquí porque no queremos una leyenda de importancia ya que los modelos no informan ninguna medida de incertidumbre.
También es posible suprimir algunas de las medidas GOF y / o utilizar coeficientes estandarizados:
screenreg(list(model.output.1, model.output.2), custom.note = "",
include.deviance = FALSE, include.auc = FALSE, standardized = TRUE)
El resultado:
======================================
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
======================================
Otras ranuras que se pueden usar con
createTexreg
La función constructora
createTexreg
se llama dentro de cualquier método de
extract
.
El ejemplo anterior muestra algunas piezas simples de información.
Además de los detalles contenidos en el ejemplo, las siguientes ranuras están disponibles en objetos
texreg
:
- se: errores estándar
- valores: los valores p
- ci.low: límite inferior de un intervalo de confianza
- ci.up: límite superior de un intervalo de confianza
- model.name: el título del modelo actual
Tenga en cuenta que deben usarse intervalos de confianza o errores estándar y valores p, no ambos.
Manipulando objetos
texreg
Además de entregar los modelos a las
screenreg
,
texreg
o
htmlreg
directamente, es posible guardar primero la información extraída en un objeto
texreg
y manipularla antes de mostrar o guardar la tabla.
El valor agregado es que incluso los cambios complejos en una tabla son fáciles de aplicar de esta manera.
Por ejemplo, es posible cambiar el nombre de los coeficientes o las estadísticas GOF, agregar nuevas filas, cambiar el nombre de un modelo, modificar los valores o cambiar el orden de los coeficientes o las estadísticas GOF.
Así es como puede hacer eso: Primero, llame a la función de
extract
para guardar la información en un objeto
texreg
:
tr <- extract(model.output.1)
Puede mostrar el contenido del objeto
texreg
simplemente llamando a
tr
, que muestra el siguiente resultado:
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
Alternativamente, esta es la estructura del objeto como se muestra por
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)
Ahora puede manipular este objeto de manera arbitraria, por ejemplo, agregar una estadística GOF:
[email protected] <- c([email protected], "new statistic")
tr@gof <- c(tr@gof, 12)
[email protected] <- c([email protected], FALSE)
O puede cambiar el orden de los coeficientes:
[email protected] <- [email protected][c(4, 1, 2, 3)]
tr@coef <- tr@coef[c(4, 1, 2, 3)]
Cuando haya terminado con las manipulaciones, puede entregar el objeto
texreg
lugar del modelo original cuando llame, por ejemplo,
screenreg
:
screenreg(list(tr, model.output.2), custom.note = "")
El nuevo resultado se verá así:
======================================
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
======================================
TL; DR
texreg
puede ser personalizado por los usuarios.
Simplemente escriba un método de extracción como el que se muestra arriba y regístrelo usando una llamada
setMethods
.
He incluido el método
H2OBinomialModel
en la
última versión de
texreg 1.36.13, junto con una corrección de errores para usar modelos sin errores estándar (como este).