Quarto ggplot2 font sizes increase with chunk option fig.width and layout-ncol - ggplot2

I use an #| layout-ncol option in quarto to place figures side-by-side. However, in combination with the chunk option fig.width the font size is changing as well. It seems as if the plot is zoomed in. I am searching for quite some time for a solution but was not able to figure out how to maintain all font sizes at a fixed size. I produced a little example illustrating the problem:
Only difference between first and second chunk is basically the addition of fig.width=3 in the second chunk.
---
format:
html:
theme: default
---
```{r echo=FALSE, message=FALSE, warning=FALSE, fig.asp=1}
#| layout-ncol: 2
library(ggplot2)
data <- data.frame(
x = c(1:10),
y = c(11:20)
)
ggplot(data=data, aes(x=x, y=y)) +
geom_smooth(method = 'lm') + geom_point() + theme_classic() +
theme(axis.title = element_text(
size = 10)) +
ylab("Y-Axis") +
xlab("X-Axis")
ggplot(data=data, aes(x=x, y=y)) +
geom_smooth(method = 'lm') + geom_point() + theme_classic() +
theme(axis.title = element_text(
size = 10)) +
ylab("Y-Axis") +
xlab("X-Axis")
```
```{r echo=FALSE, message=FALSE, warning=FALSE, fig.asp=1, fig.width=3}
#| layout-ncol: 2
ggplot(data=data, aes(x=x, y=y)) +
geom_smooth(method = 'lm') + geom_point() + theme_classic() +
theme(axis.title = element_text(
size = 10)) +
ylab("Y-Axis") +
xlab("X-Axis")
ggplot(data=data, aes(x=x, y=y)) +
geom_smooth(method = 'lm') + geom_point() + theme_classic() +
theme(axis.title = element_text(
size = 10)) +
ylab("Y-Axis") +
xlab("X-Axis")
```
Would be happy for any help!

Ok, I finally just found the answer myself. When using #| layout-ncol and no fig.width is defined for that chunk, the ggplot seems to be rescaled in a way that results in smaller font sizes of the plots text elements. So the solution is just to always define a fig.width that fits the respective columns where ggplots are displayed. I think there is a clear explenation for that, but I don't it. :D

Related

ggplot facet different Y axis order based on value

I have a faceted plot wherein I'd like to have the Y-axis labels and the associated values appear in descending order of values (and thereby changing the order of the labels) for each facet. What I have is this, but the order of the labels (and the corresponding values) is the same for each facet.
ggplot(rf,
aes(x = revenues,
y = reorder(AgencyName, revenues))) +
geom_point(stat = "identity",
aes(color = AgencyName),
show.legend = FALSE) +
xlab(NULL) +
ylab(NULL) +
scale_x_continuous(label = scales::comma) +
facet_wrap(~year, ncol = 3, scales = "free_y") +
theme_minimal()
Can someone point me to the solution?
The functions reorder_within and scale_*_reordered from the tidytext package might come in handy.
reorder_within recodes the values into a factor with strings in the form of "VARIABLE___WITHIN". This factor is ordered by the values in each group of WITHIN.
scale_*_reordered removes the "___WITHIN" suffix when plotting the axis labels.
Add scales = "free_y" in facet_wrap to make it work as expected.
Here is an example with generated data:
library(tidyverse)
# Generate data
df <- expand.grid(
year = 2019:2021,
group = paste("Group", toupper(letters[1:8]))
)
set.seed(123)
df$value <- rnorm(nrow(df), mean = 10, sd = 2)
df %>%
mutate(group = tidytext::reorder_within(group, value, within = year)) %>%
ggplot(aes(value, group)) +
geom_point() +
tidytext::scale_y_reordered() +
facet_wrap(vars(year), scales = "free_y")

Add space argument to facet_wrap

facet_wrap() has been recognized for not having a space = "free" argument (https://github.com/tidyverse/ggplot2/issues/2933). This can causes spacing issues on the y-axis of plots.
Create the above figure using the following code:
library(tidyverse)
p <-
mtcars %>%
rownames_to_column() %>%
ggplot(aes(x = disp, y = rowname)) + geom_point() +
facet_wrap(~ carb, ncol = 1, scales = "free_y")
facet_grid on the other hand has a space = "free" argument. Allowing for nice y-axis spacing.
Create the above figure using the following code:
p <-
mtcars %>%
rownames_to_column() %>%
ggplot(aes(x = disp, y = rowname)) + geom_point() +
facet_grid(carb ~ ., scales = "free_y", space = "free_y")
The issue with this is that the label is on the side, not the top. I sometimes have longer facet labels and few rows in the facet. This means the facet label gets cut off.
There is a solution from the ggforce package (comment by ilarischeinin on https://github.com/tidyverse/ggplot2/issues/2933).
p <-
mtcars %>%
rownames_to_column() %>%
ggplot(aes(x = disp, y = rowname)) + geom_point()
p + ggforce::facet_col(vars(carb), scales = "free_y", space = "free")
But, there are limitations leaving ggplot2. For example, I ultimately want a two column figure, and this functionality does not seem possible with ggforce. Is there any way to produce the same result using facet_wrap() so that I can utilize the ncol() argument?
Here is a potential workaround based on https://stackoverflow.com/a/29022188/12957340 :
library(tidyverse)
library(gtable)
library(grid)
p1 <- mtcars %>%
rownames_to_column() %>%
ggplot(aes(x = disp, y = rowname)) + geom_point() +
facet_grid(carb ~ ., scales = "free_y", space = "free_y") +
theme(panel.spacing = unit(1, 'lines'),
strip.text.y = element_text(angle = 0))
gt <- ggplotGrob(p1)
panels <-c(subset(gt$layout, grepl("panel", gt$layout$name), se=t:r))
for(i in rev(panels$t-1)) {
gt = gtable_add_rows(gt, unit(0.5, "lines"), i)
}
panels <-c(subset(gt$layout, grepl("panel", gt$layout$name), se=t:r))
strips <- c(subset(gt$layout, grepl("strip-r", gt$layout$name), se=t:r))
stripText = gtable_filter(gt, "strip-r")
for(i in 1:length(strips$t)) {
gt = gtable_add_grob(gt, stripText$grobs[[i]]$grobs[[1]], t=panels$t[i]-1, l=5)
}
gt = gt[,-6]
for(i in panels$t) {
gt$heights[i-1] = unit(0.8, "lines")
gt$heights[i-2] = unit(0.2, "lines")
}
grid.newpage()
grid.draw(gt)
Created on 2021-12-15 by the reprex package (v2.0.1)
It's not clear to me what you mean by "I ultimately want a two column figure", but if you can come up with an example to illustrate your 'ultimate' expected outcome I can try to adapt this approach and see if it will work or not.

double geom_bar, how to get the values for each bar

I have a ggplot of countries (X axis) over two different time periods (Y axis), so double bar for each country.
I would like to see the values of each bar. I used geom_text but I get the values on the same line so they are not in place. How can I use geom_text for this type of plot ?
Rcountry %>%
gather("Type", "Value",-Country) %>%
ggplot(aes(Country, Value, fill = Type)) +
geom_bar(position = "dodge", stat = "identity") +
coord_flip()+
theme_minimal()+scale_fill_grey()+
theme(legend.position="bottom")+
theme(legend.title = element_blank())+
scale_fill_manual(values=c("darkslategray4", "darkslategrey"))+
labs(x="Country", y="Stock of robots per thousands worker in '000")+
geom_text(aes(label=c(X2010, X2018)), size=3.5)```
Thank you
This can be achieved by adding position = position_dodge(.9) to geom_text, i.e. you have to the positioning used in geom_bar to geom_text to get the labels right. Using mtcars as example data, try this:
library(ggplot2)
library(dplyr)
mtcars2 <- mtcars %>%
group_by(cyl, gear) %>%
summarise(mpg = mean(mpg)) %>%
ungroup()
ggplot(mtcars2, aes(x = factor(cyl), mpg, fill = factor(gear))) +
geom_bar(position = "dodge", stat = "identity") +
theme_minimal() +
scale_fill_grey() +
theme(legend.position="bottom")+
theme(legend.title = element_blank())+
labs(x="Country", y="Stock of robots per thousands worker in '000")+
geom_text(aes(label = mpg), position = position_dodge(.9), size=3.5) +
coord_flip()
Created on 2020-04-15 by the reprex package (v0.3.0)

ggplot grobs align with tableGrob

I'm having difficulty to find solution for aligning ggplot grob and table grob. I tried to follow the instruction here but still didn't give the results I wanted.
library(grid)
library(gridExtra)
library(ggplot2)
library(tibble)
library(gtable)
dat <- tibble::rownames_to_column(mtcars, "car") #convert rownames to first col
plot1 <- ggplot(dat, aes(car, mpg)) +
geom_bar(stat = "identity") +
coord_flip()
g1 <- ggplotGrob(plot1)
tb1 <- tableGrob(dat$cyl)
g1 <- gtable_add_cols(g1, unit(0.2, "npc"))
g1 <- gtable_add_grob(g1, grobs = tb1, t=3, l=ncol(g1), b=6, r=ncol(g1))
grid.newpage()
grid.draw(g1)
I would like that each cell in the table be aligned to related bar in histogram, but still couldn't understand how the t,l,b,r be implemented from the layout.This is the output I got
I had a similar question as above when trying to make something like a forestplot in R using ggplot2 and didn't find any of the other solutions fit my needs. The answer above didn't work for me - the table didn't show up. So I hacked together a codewise not that pretty solution, but I actually kind of like the cleanliness visual output.
The things I like about this solution are:
I aligned a set of custom text not in a table, but just in a figure on the right, where the alignment matched for each text entry and each label in the figure.
I used a centered ggtitle to align a "column heading" above each set of text. These could be strings of any kind (in my actual use, I had point estimates and confidence intervals).
library(gridExtra)
library(ggplot2)
dat <- data.frame(
label = c("A", "B", "C"),
point_est = c(1,2,3),
lb_ci = c(.5, 1.5, 2.5),
ub_ci = c(1.5, 2.5, 3.5),
n = c(50, 100, 150),
total = c(75, 150, 200)
)
plot1 <- ggplot(dat, aes(x=point_est, y=label)) +
geom_point() +
geom_errorbarh(aes(xmin=lb_ci, xmax=ub_ci), height=.5) +
ggtitle("Some measure") +
ylab(NULL) + xlab("some effect estimate")
tab_base <- ggplot(dat, aes(y=label)) +
ylab(NULL) + xlab(" ") +
theme(plot.title = element_text(hjust = 0.5, size=12), ## centering title on text
axis.text.x=element_text(color="white"), ## need text to be printed so it stays aligned with figure but white so it's invisible
axis.line=element_blank(),
axis.text.y=element_blank(),axis.ticks=element_blank(),
axis.title.y=element_blank(),legend.position="none",
panel.background=element_blank(),panel.border=element_blank(),panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),plot.background=element_blank())
tab1 <- tab_base +
geom_text(aes(x=1, label=n)) +
ggtitle("n")
tab2 <- tab_base +
geom_text(aes(x=1, label=total)) +
ggtitle("total")
lay <- matrix(c(1,1,1,1,1,1,2,3), nrow=1)
grid.arrange(plot1, tab1, tab2, layout_matrix = lay)
By default the cell heights have absolute sizes to accommodate the text, but you can change them to relative units so that they scale with the plot panel,
library(grid)
library(gridExtra)
library(ggplot2)
library(tibble)
library(gtable)
dat <- tibble::rownames_to_column(mtcars, "car") #convert rownames to first col
plot1 <- ggplot(dat, aes(car, mpg)) +
geom_bar(stat = "identity") +
coord_flip()
g1 <- ggplotGrob(plot1)
tb1 <- tableGrob(dat$cyl, theme = ttheme_default(10))
tb1$heights = unit(rep(1/(nrow(tb1)), nrow(tb1)), "npc")
tb1$widths = unit.pmax(tb1$widths, unit(2, "lines"))
g1 <- gtable_add_cols(g1, sum(tb1$widths))
g1 <- gtable_add_grob(g1, grobs = tb1, t=6, l=ncol(g1), b=6, r=ncol(g1))
grid.newpage()
grid.draw(g1)

Is it possible to have 2 legends for variables when one is continuous and the other is discrete?

I checked a few examples online and I am not sure that it can be done because every plot with 2 different variables (continuous and discrete) has one of 2 options:
legend regarding the continuous variable
legend regarding the discrete variable
Just for visualization, I put here an example. Imagine that I want to have a legend for the blue line. Is it possible to do that??
The easiest approach would be to map it to a different aesthetic than you already use:
library(ggplot2)
ggplot(mtcars, aes(x = mpg, y = hp)) +
geom_point(aes(colour = as.factor(gear), size = cyl)) +
geom_smooth(method = "loess", aes(linetype = "fit"))
There area also specialised packages for adding additional colour legends:
library(ggplot2)
library(ggnewscale)
ggplot(mtcars, aes(x = mpg, y = hp)) +
geom_point(aes(colour = as.factor(gear), size = cyl)) +
new_scale_colour() +
geom_smooth(method = "loess", aes(colour = "fit"))
Beware that if you want to tweak colours via a colourscale, you must first add these before calling the new_scale_colour(), i.e.:
ggplot(mtcars, aes(x = mpg, y = hp)) +
geom_point(aes(colour = as.factor(gear), size = cyl)) +
scale_colour_manual(values = c("red", "green", "blue")) +
new_scale_colour() +
geom_smooth(method = "loess", aes(colour = "fit")) +
scale_colour_manual(values = "purple")
EDIT: To adress comment: yes it is possible with a line that is data independent, I was just re-using the data for brevity of example. See below for arbitrary line (also should work with the ggnewscale approach):
ggplot(mtcars, aes(x = mpg, y = hp)) +
geom_point(aes(colour = as.factor(gear), size = cyl)) +
geom_line(data = data.frame(x = 1:30, y = rnorm(10, 200, 10)),
aes(x, y, linetype = "arbitrary line"))