How to wrap lengthy labels in multiple panels in grouped bar charts - ggplot2

This question pertains to how to wrap lengthy labels in clustered column/bar chart to have the labels appear on multiple lines (or rows) in multiple panels. Consider the data below
df <- data.frame(group=c("Treated very satisfied", "Treated very satisfied",
"Treated not satisfied","Treated not satisfied",
"Untreated very satisfied","Untreated very satisfied",
"Untreated not satisfied","Untreated not satisfied"),
cost=c("low","high","low","high","low","high","low","high"),
treatment=c("treated","treated","treated","treated",
"untreated","untreated","untreated","untreated") ,
value=c(2.3,5.7,4.0,3.1,9.4,3.1,2.0,-1.6))
We use group as the x axis and value as the y axis. From this data set, the following code is used generate the subsequent chart
#REORDER
df$group <- factor(df$group, levels = c("Treated very satisfied",
"Treated not satisfied",
"Untreated very satisfied",
"Untreated not satisfied"))
ggplot(data=df,aes(y = value, x = group, fill = cost)) +
geom_bar(stat="identity",position='stack') + ylab("Y label") +
theme(legend.direction = "horizontal",legend.position = "bottom",
legend.spacing.x = unit(0.1, 'cm'))+
theme(legend.title=element_blank())+
geom_text(aes(label = ifelse(value !=0, value, "")),
position = position_stack(vjust=0.5))+
facet_grid( ~ treatment)
On the x axis, I expect to see "Treated very satisfied" and "Treated not satisfied" in panel 1 and
"Untreated very satisfied" and "Untreated not satisfied" in panel 2. As shown, these texts overlap, so they cannot be seen clearly. I am looking for a way to format/wrap the texts (not rotate), each into multiple lines (i.e. three lines per each) along the x axis, for example, Treated\n very\n satisfied for all labels in both panels.
I have considered several attempts to resolve this, example using function "str_wrap" in package "stringr" (from Auto wrapping of labels via labeller=label_wrap in ggplot2). However, this does not work due to the panels/clusters in the plot. I would appreciate any help on this.

First I would recommend using scales = "free_x" in facet_grid to show only the categories used in each panel. Second. Using the solution in the linked post worked fine for me to wrap the labels.
library(ggplot2)
library(stringr)
df <- data.frame(group=c("Treated very satisfied", "Treated very satisfied",
"Treated not satisfied","Treated not satisfied",
"Untreated very satisfied","Untreated very satisfied",
"Untreated not satisfied","Untreated not satisfied"),
cost=c("low","high","low","high","low","high","low","high"),
treatment=c("treated","treated","treated","treated",
"untreated","untreated","untreated","untreated") ,
value=c(2.3,5.7,4.0,3.1,9.4,3.1,2.0,-1.6))
#REORDER
df$group <- factor(df$group, levels = c("Treated very satisfied",
"Treated not satisfied",
"Untreated very satisfied",
"Untreated not satisfied"))
ggplot(data=df,aes(y = value, x = group, fill = cost)) +
geom_bar(stat="identity",position='stack') + ylab("Y label") +
theme(legend.direction = "horizontal",legend.position = "bottom",
legend.spacing.x = unit(0.1, 'cm'))+
theme(legend.title=element_blank())+
geom_text(aes(label = ifelse(value !=0, value, "")),
position = position_stack(vjust=0.5))+
scale_x_discrete(labels = function(x) str_wrap(x, width = 10)) +
facet_grid( ~ treatment, scales = "free_x")
Created on 2020-06-08 by the reprex package (v0.3.0)

Related

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)

Spacing between x-axis groups bigger than within group spacing geom_col

I am trying to get double the space between the groups Automatic and Manual on the x-axis compared to the spaces within these groups. I am using geom_col() and experimted with different arguments, suchs as position_dodge, width and preserve = "single". I can't get this to work. What I am aiming for is a graph such as I have added as an image.
library(ggplot2)
library(ggthemes)
library(plyr)
#dataset
df <- mtcars
df$cyl <- as.factor(df$cyl)
df$am <- as.factor(df$am)
df$am <- revalue(df$am, c("0"="Automatic", "1"="Manual"))
ggplot(df, aes(fill = cyl, x = am, y = mpg)) +
geom_col(position = position_dodge(width = 0.9)) +
theme_bw()
Try using a combination of position=position_dodge(width=...) and width=...
For example:
ggplot(df, aes(fill = cyl, x = am, y = mpg)) +
geom_col(position = position_dodge(width = 0.9), width=0.8) +
theme_bw()
The width() command gives the displayed width of individual bars, while the position(width=) gives the space that is reserved for the bars.
The difference between the two values gives the space between bars within a group, while 1 - position_dodge(width=) gives the space between the groups.

Using 2nd variable to label axis ticks in ggplot2 facets

I am attempting to make a faceted t-chart using ggplot2, where the x-axis is represented a sequence of events and the y-axis represents the number of days between those events. The x-axis should be labelled with the event date, but it is not a time series since the distance between x-axis ticks should be uniform regardless of the real time between events.
Adding a faceting layer has been confusing me. Here's some sample data:
df <- data.frame(EventDT = as.POSIXct(c("2014-11-22 07:41:00", "2015-02-24 08:10:00",
"2015-06-10 13:54:00", "2015-07-11 02:43:00",
"2015-08-31 19:08:00", "2014-11-18 14:06:00",
"2015-06-09 23:10:00", "2016-02-29 07:55:00",
"2016-05-22 04:30:00", "2016-05-25 21:46:00",
"2014-12-22 16:19:00", "2015-05-13 16:38:00",
"2015-06-01 09:05:00", "2016-02-21 02:30:00",
"2016-05-13 01:36:00")),
EventNBR = rep(1:5, 3),
Group = c(rep('A', 5), rep('B',5), rep('C',5)),
Y = c(15.818750, 94.020139, 106.238889, 30.534028, 51.684028,
187.670139, 203.377778, 264.364583, 82.857639, 3.719444,
169.829861, 142.013194, 18.685417, 264.725694,81.962500))
Ignoring the date of the event, I can produce this:
g <- ggplot(df, aes(x=EventNBR, y=Y)) +
geom_point() +
geom_line() +
facet_wrap(~ Group, scales='free_x')
Plot should show EventDT along X-axis, not EventNBR
I have tried to use the labels parameter to scale_x_discrete without success:
xaxis.ticks <- function(x) {
df[which(df$EventNBR) == x] }
g + scale_x_discrete(labels = xaxis.ticks)
But that's wrong in a way I can't describe, because it cuts off my tick labels altogether.
Because there is a 1-1 correspondence between EventNBR and EventDT by Group for this dataset, it seems like there should be an easy solution, but I can't figure it out. Am I overlooking something painfully easy?
In general, this is a very problematic thing as mentioned here and there are several other topics on this.
But luckily in your case it is possible since you use scales='free_x'.
What you need to do is adding an unique index column like
df$id <- 1:nrow(df)
and afterwards you can overwrite these indexes with you column with correct labels.
g <- ggplot(df, aes(x=id, y=Y)) +
geom_point() +
geom_line() +
facet_wrap(~ Group, scales='free_x')
g + scale_x_continuous(breaks=df$id, labels=df$EventDT) +
theme(axis.text.x=element_text(angle=90, vjust=.5))
There might be easier solutions but this is working in your example.
Also, the labels seem to be gone since the x axis is numeric and not discrete. So using scale_x_continuous produces the correct labels.
EDIT:
So a full example looks like this
library(ggplot2)
df <- data.frame(EventDT = as.POSIXct(c("2014-11-22 07:41:00", "2015-02-24 08:10:00",
"2015-06-10 13:54:00", "2015-07-11 02:43:00",
"2015-08-31 19:08:00", "2014-11-18 14:06:00",
"2015-06-09 23:10:00", "2016-02-29 07:55:00",
"2016-05-22 04:30:00", "2016-05-25 21:46:00",
"2014-12-22 16:19:00", "2015-05-13 16:38:00",
"2015-06-01 09:05:00", "2016-02-21 02:30:00",
"2016-05-13 01:36:00")),
EventNBR = rep(1:5, 3),
Group = c(rep('A', 5), rep('B',5), rep('C',5)),
Y = c(15.818750, 94.020139, 106.238889, 30.534028, 51.684028,
187.670139, 203.377778, 264.364583, 82.857639, 3.719444,
169.829861, 142.013194, 18.685417, 264.725694,81.962500))
df$id <- 1:nrow(df)
g <- ggplot(df, aes(x=id, y=Y)) +
geom_point() +
geom_line() +
facet_wrap(~ Group, scales='free_x')
g + scale_x_continuous(breaks=df$id, labels=df$EventDT) +
theme(axis.text.x=element_text(angle=90, vjust=.5))
and produces the following output:

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)