在没有创建它的代码的情况下从 ggplot object 更改功能

Change features from ggplot object without the code that created it

我正在尝试了解有关 ggplot object 的工作原理的更多信息。我试图在没有初始代码的情况下实现更改 ggplot object 的目标。所以在这个例子中我使用 sjPlot 输出模型 object model_obj。通过不依赖用于制作情节的初始代码,我希望能够改变情节的 3 件事。我希望能够

  1. 将图例标题从“因变量”更改为“结果”(我真的不在乎它叫什么它只是一个测试)
  2. Re-order y 轴上的系数
  3. 更改 x 轴边界

我知道 plot_models 函数已经允许其中的一些功能,但我正在尝试了解更多有关 ggplot 工作原理的信息。 我在网上找到的一个想法是,人们使用此 ggplot_build 函数从绘图 object 中提取一些特征,然后对其进行更改,然后将其还原为具有 ggplot_gtable 的绘图。但是,我不明白输出的哪些部分对应于上面的 3 个东西。

我也对使用 + 符号向 ggplot object 添加新功能的选项感兴趣

model_obj + ggtitle("New title") 

但我不确定这在模型 object 的初始代码未知的情况下是否总是有用。

这是我的代码

require(sjPlot)
data(mtcars)

# fit models
fit1 <- glm(vs ~ mpg + disp + drat,family="binomial", data=mtcars)
fit2 <- glm(am ~ mpg + disp + drat,family="binomial", data=mtcars)

# plot multiple models
model_obj<-plot_models(fit1, fit2)
model_obj

#extract features of ggplot object (One possible solution) 
q <- ggplot_build(model_obj)
q

###do something to the new q object that changes it


#get the ggplot back 
q <- ggplot_gtable(q)
plot(q)

更新:

针对上述示例提出了一个有用的解决方案。

model_obj + scale_x_discrete(limits = c("disp", "drat", "mpg") ) + scale_y_continuous(trans = "log10", limits = c(.001, 10000) ) + labs(color = "Outcomes")

但是,我发现了一种情况,它也不起作用。举个例子,

library(sjPlot)
library(titanic)
titanic_train$Sex_num<-ifelse(titanic_train$Sex=="male", 0, 1)
fit1 <- glm(Survived ~ Embarked + Fare,family="binomial", data=titanic_train)
fit2 <- glm(Sex_num ~ Embarked + Fare,family="binomial", data=titanic_train)
model_obj<-plot_models(fit1, fit2)
model_obj + scale_x_discrete(limits = c("Embarked", "Fare") ) + scale_y_continuous(trans = "log10", limits = c(.001, 10000) ) + labs(color = "Outcomes")

在这种情况下,似乎存在一个小故障,即不再绘制估计值。我猜这与重新排序部分有关,用于估计具有参考类别的单个变量。

从概念上讲,逆向工作可能最容易。

当在绘图 window 上绘制 ggplot 时,实际绘制的是图形对象或“grobs”的集合,它们是简单的几何形状(点、线、多边形和文本)作为由 grid 包定义和渲染。从某种意义上说,ggplot 完全 sub-contracts 出其绘图的实际渲染 grid。相反,ggplot 的工作是准确计算出哪里需要哪些 grob。因此,您可以将 ggplot 的最终产品视为 grob 的集合。此集合是您在 "ggplot_built" 对象上调用 ggplot_gtable 时获得的 gtable。

一旦实际构建了 grobs 集合,就可以对其进行适当的更改,但这样做相对困难,因为您正在处理相互依赖的几何对象的深度嵌套列表。这样做总是感觉有点“hacky”。如果可以的话,还是尽量在这个阶段之前把剧情搞好吧。

为了创建 gtable,ggplot 需要一个关于如何构建这个 grob 集合的完整蓝图。这个最终蓝图就是 "ggplot_built" 对象。然而,在这个阶段,又要考虑许多相互依赖的结构:它是一个复杂的 ggproto 对象,具有嵌套的数据、属性和函数,并且很容易被破坏。因此,更改 ggplot_built 对象也很困难。

在大多数情况下,我们希望在构建蓝图之前更改地块的规格。规范是实际的“ggplot”对象。如果我们制作一个非常简单的 ggplot 对象并将其存储为变量,则不会绘制任何内容 window,也不会构建 ggplot_builtgtable 对象。 ggplot 仍处于规范阶段,可以轻松更改

df <- data.frame(x = 1:10, y = 1:10)

p <- ggplot(df, aes(x = x, y = y)) + geom_point()

只有当我们在对象上隐式或显式调用 plot 时,最终的 specification -> blueprint -> grobs -> drawing 才会发生。

在你的例子中,model_obj 实际上是一个 ggplot 对象,所以你可以很容易地改变它的参数。例如,如果我想将其更改为极坐标 co-ordinates,我可以直接执行 model_obj + coord_polar()(并收到警告,提示我尝试应用两个 coords),或者直接覆盖坐标。我会坚持只在此处添加它们。

model_obj$coordinates + coord_polar()
model_obj

同样,如果我想更改色标的图例,我可以添加或替换色标对象。从现在开始,我将覆盖以保持更改。

model_obj$scales$scales[[3]] <- scale_color_manual(values = c("blue", "red"), name = "Outcome")
model_obj

现在,要重新排序 y 轴(实际上是 x 轴,因为 sjplot 对象已翻转 co-ordinates,我可以这样做:

model_obj$scales$scales[[1]] <- scale_x_discrete(limits = c("mpg", "disp", "drat"))
model_obj

最后,我们像这样更改 x 轴范围:

model_obj$scales$scales[[2]] <- scale_y_log10(limits = c(0.0001, 100))
model_obj

我找到了第二种情况的解决方案。我猜这不是 ggplot 故障,实际上是 sjplot 功能。

之所以没有绘制图表,是因为您需要为出现在图表上的变量使用准确的标签。所以你做不到

model_obj + scale_x_discrete(limits = c("Embarked", "Fare") )

因为只有 Fare 符合其中一个标签。见下文

library(sjPlot)
library(titanic)
titanic_train$Sex_num<-ifelse(titanic_train$Sex=="male", 0, 1)
titanic_train$Embarked_test<-ifelse(titanic_train$Embarked=="C", "C", ifelse(titanic_train$Embarked=="Q", "Q", ifelse(titanic_train$Embarked=="S", "S", NA)))


fit1 <- glm(Survived ~ Embarked_test + Fare,family="binomial", data=titanic_train)
fit2 <- glm(Sex_num ~ Embarked_test + Fare,family="binomial", data=titanic_train)


model_obj<-plot_models(fit1, fit2)

model_obj + scale_x_discrete(limits = c("Fare", "Embarked_testS", "Embarked_testQ") ) + scale_y_continuous(trans = "log10", limits = c(.001, 10000) ) + labs(color = "Outcomes")