prevent custom text to increase plot size - r

I am writing some text on my plot, which works perfectly fine. Now I realized that if I put the text further down the y-axis, the plot space somehow becomes larger. This is not what I want. The following 2 plots illustrate the issue. The first one puts the text at y = 0, whereas the second one puts it at y = the min-0.25, so roughly where the plot space begins vertically. Is it possible to keep the plot space as in the first plot, but still to write at the bottom? If there is no intended way to adjust that, I would also be happy with a workaround.
set.seed(12)
test <- data.table(x = rnorm(29*2),var=c(rep("x1",29),rep("x2",29)),
time=rep(seq(as.Date("1983/12/31"),as.Date("2011/12/31"), "year"),2))
library(ggplot2);library(scales)
ggplot(data=test,aes(x=time, y=x, colour=var)) +
geom_line() + scale_x_date(date_labels="%Y",date_breaks = "3 years") +
geom_text(aes(x=as.Date("1988-04-30"), label="Text which does not increases space", y=0,
fontface="plain"), angle=60, colour="black",vjust=0,hjust=0,size = 4)
ggplot(data=test,aes(x=time, y=x, colour=var)) +
geom_line() + scale_x_date(date_labels="%Y",date_breaks = "3 years") +
geom_text(aes(x=as.Date("1988-04-30"), label="Text which increases space", y=min(test[,x])-0.25,
fontface="plain"), angle=60, colour="black",vjust=0,hjust=0,size = 4)

As any other geom, geom_text expands the plotting area in cases like this. Then it's necessary to manually restrict the y axis with ylim(range(test$x)). However, that's not enough, as then the text wouldn't be fully visible and, as a result, ggplot completely drops it. To fix this, we need clip = "off". Thus, adding
coord_cartesian(clip = "off", ylim = range(test$x))
gives
where now axes are unchanged.

Related

Insert rectangle outside of ggplot to visualize plot segments

I hope you can help me. I have the idea of visualizing segments within a plot with a rectangle that can be placed next to the y or x-axis which means that it would be outside of the plot area. It should look similar as in the image below:
I tried to reach the mentioned output by trying two different approaches:
I created two viewports with the grid package and put the plot in one viewport that I placed at the bottom and one viewport on top of that. The big problem here is that I need the coordinates from where the grey background panel of the ggplot starts so I can place the top viewport exactly there, so that the segments conincide with the x-axis length. My code looked like following:
container_viewport <- viewport(x=0,y=0,height=1,width=1,just = c("left","bottom"))
pushViewport(container_viewport)
grid.draw(rectGrob())
popViewport()
section_viewport <- viewport(x=0.055,y=0.99,height=0.085,width=0.935,just=c("left","top"))
pushViewport(section_viewport)
plot_obj <- ggplot_build(testplot)
plot_data <- plot_obj$data[[1]]
grid.draw(rectGrob(gp = gpar(col = "red")))
popViewport()
plot_viewport <- viewport(x=0,y=0,height=0.9,width=1,just=c("left","bottom"))
pushViewport(plot_viewport)
grid.draw(ggplotGrob(testplot))
popViewport()
This looks fine but I had to hardcode the coordinates of the viewport at the top.
I used grid.arrange() to arrange to stack the plots vertically (instead of a grob for the rectangle like in the other approach I create a ggplot instead for that). Here, basically the same problem exists, since I somehow need to put the plot representing the rectangle at the top in the right position on the x-axis. My code looked like following:
p1 <- plot_data %>%
ggplot()+
geom_rect(aes(xmin=-Inf,xmax=Inf,ymin=-Inf,ymax=Inf))
p2 <- testplot
test_plot <- grid.arrange(p1,p2,heights=c(1,10))
This approach does not work that good.
Since I would like to create a solution that can be applied generally, trial and error with the coordinates of the viewport is no option since the length of the y-axis label or tick labels can vary and therefore the length and coordinates of the background panel. When this step is done the segmentation of the rectangle should be no problem anymore.
Maybe this is just not possible but if then I would appreciate any help.
Thank you!
I would probably use patchwork here. Let's start by replicating your plot:
library(ggplot2)
library(patchwork)
p <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
geom_point(color = "red") +
labs(x = "test", y = "test")
p
That looks very similar. Now we define (in our own co-ordinates) where we want the section split to occur on the x axis.
section_split <- 5.25
Using just this number, we add rectangles and text annotations that cover a copy of our original plot, and remove its axis annotations using theme_void:
p2 <- p +
annotate("rect", xmin = c(-Inf, section_split), ymin = c(-Inf, -Inf),
xmax = c(section_split, Inf), ymax = c(Inf, Inf),
fill = c("#00a2e8", "#ff7f27")) +
annotate("text", label = c("Section A", "Section B"), size = 6,
y = rep(mean(layer_scales(p)$y$range$range), 2),
x = c((min(layer_scales(p)$x$range$range) + section_split)/2,
(max(layer_scales(p)$x$range$range) + section_split)/2)) +
theme_void()
Now we just draw this second plot above our first, adjusting the relative heights to about 1:10
p2/p + plot_layout(heights = c(1, 10))
The benefit of doing it this way is that, since we copied the original plot, the positional mapping of the x axis is identical between the two plots, and patchwork will automatically line up the panels.
Created on 2023-02-04 with reprex v2.0.2

Adjusting position on discrete x-axis of two goups in ggplot

I have a plot with a continous y-axis and discrete x-axis.
For the data I have a group factor with 3 levels and 2 meausement points, so 6 geoms are created
1
I would like to keep the width of the single geoms but adding space between the two measurement points, respectively the two groups of geoms. Like: 3 geoms - gap - 3 geoms. Is there any possibility of adjusting the position of a group of geoms on the x-axis in ggplot?
preferences %>%
pivot_longer(c(F1_life_satisfaction_pre, F1_life_satisfaction_current), names_to = "variables", values_to = "ratings")%>%
ggplot( aes(y=ratings, x=fct_inorder(variables), fill=fct_inorder(playing_preference))) +
geom_violin(scale="width", adjust=0.5, width=0.8, alpha= 0.2, position = position_dodge(1)) +
stat_summary(fun=mean, geom="point", shape=23, size=2, position = position_dodge(1)) +
stat_summary(aes(group=fct_inorder(playing_preference)), fun=mean, geom = "line", size= 0.5, position = position_dodge(1)) +
stat_summary(fun.data=mean_cl_normal, fun.args=list(mult=1),aes(x=fct_inorder(variables), y=ratings), geom="errorbar",
width=0.05, position = position_dodge(1)) +
scale_x_discrete(labels = c("pre-Pokemon-Go", "current"),expand = c(0, 0.3)) +
theme(axis.text.x = element_text(color = "black", size=10)) +
scale_y_continuous(breaks = c(1, 2, 3, 4, 5, 6, 7), limits=c(1,7)) +
geom_segment(aes(x = 0, y=4, yend=4, xend=3), color="grey") +
theme(axis.ticks.x = element_blank()) +
labs(fill = "playing preference") +
labs(x="life satisfaction") +
theme(axis.title = element_text(size = 10))+
theme(legend.text = element_text(size = 10)) +
theme(legend.title = element_text(size = 10)) +
labs(y = "mean ratings") +
geom_boxplot(width=0.1,color="black", alpha=0.2, position = position_dodge(1)) +
scale_fill_viridis(discrete=T)
TL;DR - play with width= and position_dodge(width=...) within your line for geom_violin and geom_boxplot to adjust the positions along with scale_x_discrete(expand=expansion(...).
The first point is that the resolution (and how close and far apart) things are on your plot will be related to the size of your window. With that being said, the positioning relationship of the plot elements between one another can be controlled via ggplot. In particular, you want to change the values of width= and position_dodge(width=...) in your geom_violin call (and your geom_boxplot call).
Example Dataset
I'll use an example dataset to illustrate the idea, where I'll plot boxplots... but the idea is identical. The example dataset contains two x values ("Group1" and "Group2"), and each of those has subdivisions that are either "A", "B", or "C", containing a separate normal distribution of 50 datapoints for every x and x.subdiv.
set.seed(8675309)
df <- data.frame(
x=c(rep('Group1', 150), rep('Group2', 150)),
x.subdiv=rep(c(rep('A', 50), rep('B',50), rep('C',50)), 2),
y=unlist(lapply(1:6, function(x){rnorm(50, runif(1,10,15), runif(1,0,7))}))
)
Width of position_dodge
Here's the simple boxplot, where I'll use 0.5 as the value for both width= and position_dodge(width=...). Note that the first argument in position_dodge is width=, so you can just supply that number directly to that function without explicitly assigning to the width argument.
p <- ggplot(df, aes(x=x, y=y)) + theme_bw()
p + geom_boxplot(aes(fill=x.subdiv), width=0.5, position=position_dodge(0.5))
The rule to note here is:
geom_boxplot(width=...) controls how wide the overall spread of box plots are around each x= value.
position_dodge(width=...) controls the amount of spread (the amount of "dodging") for the groups around the x= aesthetic.
So this is what happens when you change position_dodge(width=1), but leave geom_boxplot(width=0.5):
p + geom_boxplot(aes(fill=x.subdiv), width=0.5, position=position_dodge(1))
The width of each box remains the same as before, but the positioning of each box around x= is more "spread out". In effect, each is "dodged" more. If you set position_dodge(width=0.2), you'll see the opposite effect, where the boxes become squished together (because they are not spread out as much around x=):
p + geom_boxplot(aes(fill=x.subdiv), width=0.5, position=position_dodge(0.2))
The interesting thing is how geom_boxplot(width=) and position_dodge(width=) are related:
If geom_boxplot(width=) is equal to position_dodge(width=), the boxes will be touching
If geom_boxplot(width=) is less than position_dodge(width=), the boxes will be separated from one another
If geom_boxplot(width=) is greater than position_dodge(width=), the boxes will be overlapping one another
Width of the geom
The width= of the geom itself relates to how wide the boxplots are. The point to keep in mind are these two points:
The width= is the sum of all the widths of the individual dodged geoms for that particular x= aesthetic.
width=1 is the width between two values on a discrete axis, meaning when you set width=1, the boxes will be wide enough to touch
That means that if we set geom_boxplot(width=1), the combined total of all the boxes for "Group1" will be wide enough to touch the boxes of "Group2"... but you would only see that if there were no overlap among the boxes (meaning that position_dodge(width=) would be equal to geom_boxplot(width=)).
So this makes the boxes wide enough to be touching, but position_dodge(width) is less than geom_boxplot(width)... so the boxes overlap, but "Group1" boxes are separated from "Group2" boxes:
p + geom_boxplot(aes(fill=x.subdiv), width=1, position=position_dodge(0.8))
If we want everything to touch, you have to set them equal, and both equal to 1:
p + geom_boxplot(aes(fill=x.subdiv), width=1, position=position_dodge(1))
Control both widths
In the end, it's probably best to control both. If we go from the previous plot, you probably want the plots to have separation between "Group1" and "Group2". That means you need to make the width of all boxes smaller (which we control by geom_boxplot(width)). However, you probably still want the dodging to leave a bit of space between the boxes, so we'll have to set position_dodge(width) to be greater than geom_boxplot(width), but not too large so that we lose the separation between "Group1" and "Group2". Something like this works pretty well:
p + geom_boxplot(aes(fill=x.subdiv), width=0.5, position=position_dodge(0.55))
In your case, you have both geom_violin and geom_boxplot, so you'll need to adjust those together and work out the proper look.
EDIT: "Shift Left and Right" and "Squish"
If the width= and position_dodge(width= arguments are just not quite getting you what you need, there is another parameter that can work in concert with them to move things around. This would be to use scale_x_discrete(expand=... to control the amount of space to the left and right of your x axis items. Used together with width= and position_dodge(width=, this actually gives you precise control of where to position your data along the x axis while still respecting the automated plotting that ggplot2 provides.
width= controls the whitespace between data along the x axis
position_dodge(width= controls the amount of whitespace between subgroups in the data positioned along the x axis
scale_x_discrete(expand=... controls white space to the left and right sides of the panel.
I'll demonstrate the functionality using the same dataset as before. Note that proper use of the expand= argument for scale_x_discrete should call expansion() and you will need to provide a 1 or 2 length vector to either add= or mult=. Play around with both and numbers to see the effect, but here's kind of what to expect.
The expansion() function takes either mult= or add= as arguments, which can either be a vector of length 2 (where 1 is applied to left side and 2 is applied to the right side, or length 1 (where the number is applied to both sides). Numbers sent to mult= are multiplied by the normal expansion to give you the new amount, so the code below sets the extra whitespace to the left and the right equal to 30% (0.3 * normal) of the typical expansion for both sides:
p + geom_boxplot(aes(fill=x.subdiv), width=0.5, position=position_dodge(0.55)) +
scale_x_discrete(expand=expansion(mult=0.3))
Sending two values, you can adjust separately. This sets the left side to be 100% (normal) and the right side to be reduced to 50% of normal:
p + geom_boxplot(aes(fill=x.subdiv), width=0.5, position=position_dodge(0.55)) +
scale_x_discrete(expand=expansion(mult=c(1,0.5)))
Bottom Line: Seems like by using all three arguments for width=, position_dodge(width=, and scale_x_discrete(expand=expansion(..., you can theoretically place your x groupings anywhere along your plot. Just keep in mind that the resolution and aspect ratio of your graphics device will change how things are laid out a bit, so additional control can be adjusted by resizing the graphics window.

X axis labels tied to histogram bars instead of following separate rules

When using a histogram with x as a POSIXct value, I'm not sure how you're supposed to line the ticks up with the binsize of the graph.
Setting the tick size to the same as the binsize makes it line a bit off, but the offset adds onto each other until its no longer accurate.
bymonth <- ggplot() +
scale_x_datetime("", breaks = date_breaks("60 days"), labels = date_format("%m-%y")) +
...
lots of geom_rects for background colors
...
theme(legend.title = element_blank()) +
geom_histogram(data=dat, aes(x = iso, fill = name), binwidth = 30*24*60*60, position = 'dodge')
I tried using annotate() as well as experimenting with the spacing of the tick but I think my approach here might be wrong in its own accord
This leads to a graph looking something like this
Which is quite annoying

vertically align asterisks (stars) in ggplot

In ggplot, I want to label some error bars with asterisks ('*') to indicate significance level. The graph is arranged with category labels on the y axis, so that they are easily legible. This means that the error bars are horizontal, and the *'s need to align vertically with them. However, the symbol '*' is not vertically centred in a line of text, so it gets plotted too high using geom_text.
Reproducible example
set.seed(123)
x = data.frame(grp = LETTERS[1:8], val = sample(10,8))
se = runif(8, 0.1,2)
x$upper = x$val + se
x$lower = x$val - se
x$labs = sample(c('*','**', '***', ''), 8, T)
gg = ggplot(x, aes(grp,val)) +
geom_point() +
geom_errorbar(aes(ymax = upper, ymin=lower), width=0.3) +
scale_y_continuous(limits = c(-2,12)) +
coord_flip()
gg + geom_text(aes(y=upper+0.2, label=labs), size=8, hjust='left')
I know that I can nudge the label position like this:
gg + geom_text(aes(y=upper+0.2, label=labs), size=8, nudge_x = -0.2, hjust='left')
However, getting the correct value of nudge_x needs to be done in an ad-hoc manner and the correct value varies with size of graphics output, font size, number of categories on the y scale etc. Is there a way to get the labels to automatically align vertically? I tried using geom_point with shape=42 instead of geom_text to draw the asterisks. Although this solves the vertical alignment issue, it introduces its own problem with getting the spacing between a horizontal row of asterisks correct (i.e. getting '**' and '***' to print with the correct separation between adjacent symbols).
Just eyeballing it on my machine, it looks like this vjust adjustment seems to work, and I think it may be fairly robust to changes in device output size, font size, etc.
gg + geom_text(aes(y=upper+0.2, label=labs), size=8, hjust='left',vjust = 0.77)

ggplot in R: barchart with log scale label misplacement

So I have a bar chart to make, and a log plot for y axis is warranted because of the data range. So the problem is I have the value of 0.5, which in log10 is -0.3.
Since the bar goes into negative, the "top" of the bar, which is used for placing the labels is actually the "bottom" and so my text label is "just above" the bottom, which means in the middle of the bar.
I figure I am probably not the first person with this issue, but searching for related fixes has not helped. Most notably, I tried using dodge, but this does not change that the "top" of the bar is really the "bottom".
So two questions:
Can I fix this label mishap?
This is just ugly: can I move the x axis up to y=1 to give more context to the negative value without moving the x axis labels?
.
alpha=c('A','B','C','D')
value=c(0.5,10,40,1100)
table<-as.data.frame(alpha)
table<-cbind(table, value)
library(ggplot2)
graph <- ggplot(table, aes(x=alpha)) +
geom_bar(stat="identity",aes(y=value),width=0.5) +
geom_text(aes(y=value,label=value),vjust=-0.5) +
scale_y_continuous(trans="log10",limits=c(0.5,1400))
graph + theme_classic()
A little trick modifying the y coordinate of the data labels (use ifelse() to set the value of y to 1 if the value is less than one). As for the axis, simply hide the X axis (setting it to element_blank()) and draw a new horizontal line:
graph <- ggplot(table, aes(x=alpha)) +
geom_bar(stat="identity",aes(y=value),width=0.5) +
# Modify the placing of the label using 'ifelse()':
geom_text(aes(y=ifelse(value < 1, 1, value),label=value),vjust=-0.5) +
scale_y_continuous(trans="log10",limits=c(0.5,1400)) +
theme_classic() +
# Hide the X axis:
theme(axis.line.x = element_blank()) +
# Draw the new axis
geom_hline()
print(graph)
The output:

Resources