How to directly label regression lines within plot frame (without a legend)? - r

Data and Previous Content
This question is a continuation of a previous question with the same data but with a slight tweak.
Question
Same as before, this is the example I am looking to achieve, with the part I want now highlighted in green:
Instead of coloring a specific regression line, now I want to add a direct label to the plot window like above. I know that by faceting the data, we can achieve this with a legend, coloring the lines, etc. We can even manually add an annotation by selecting the x and y coordinates with annotate or geom_text.
But I want something that doesn't require a legend or manually figuring out where the exact geom coordinates are. Is there a way to simply add the label to a regression line within the plot window similar to other aes functions? This is the base plot I have so far, with the label now removed and regression lines colored:
ggplot(slack.work,
aes(x=Coffee_Cups,
y=Mins_Work,
color=Month_Name))+
geom_point(alpha = .4)+
geom_smooth(method = "lm",
se = F)+
scale_colour_viridis_d()+
theme_bw()+
labs(title = "Coffee Cups x Minutes of Productivity",
subtitle = "Pearson r = .30, p < .001",
x="Cups of Coffee",
y="Minutes of Work",
color="Month")+
theme(plot.title = element_text(face = "bold",
size = 15,
family = "mono"),
plot.subtitle = element_text(face = "italic"),
legend.position = "none")
Currently, it looks like this:
But I would like for it to look something like this:

Adapting this answer to your case you could achieve your desired result by using stat="smooth" via geom_text or ggrepel::geom_text_repel. The tricky part is to get only one label for which I use an ifelse inside after_stat:
library(ggplot2)
# Levels of Month_Name.
# Needed to get the month names.
# When using after_stat only get the level number via `group`
levels_month <- levels(factor(slack.work$Month_Name))
ggplot(
slack.work,
aes(
x = Coffee_Cups,
y = Mins_Work,
group = Month_Name,
color = Month_Name == "January"
)
) +
geom_point(alpha = .4) +
geom_smooth(
data = ~subset(.x, !Month_Name == "January"),
method = "lm",
se = F
) +
geom_smooth(
data = ~subset(.x, Month_Name == "January"),
method = "lm",
se = F
) +
ggrepel::geom_text_repel(aes(label = after_stat(ifelse(x %in% range(x)[1], levels_month[group], NA_character_))),
stat = "smooth", method = "lm",
nudge_x = -.5, direction = "y") +
scale_x_continuous(expand = expansion(add = c(.5, 0), mult =.05)) +
scale_colour_manual(values = c("TRUE" = "steelblue", "FALSE" = "grey65")) +
annotate("text",
x = 3,
y = 800,
label = "January had the strongest effect on productivity.",
size = 4,
color = "steelblue"
) +
theme_bw() +
labs(
title = "Coffee Cups x Minutes of Productivity",
subtitle = "Pearson r = .30, p < .001",
x = "Cups of Coffee",
y = "Minutes of Work",
color = "Month"
) +
theme(
plot.title = element_text(
face = "bold",
size = 15,
family = "mono"
),
plot.subtitle = element_text(face = "italic")
) +
guides(color = "none")
EDIT To get rid of the segments connecting the line and the label you could add min.segment.length = Inf to geom_text_repel:
... +
ggrepel::geom_text_repel(aes(label = after_stat(ifelse(x %in% range(x)[1], levels_month[group], NA_character_))),
stat = "smooth", method = "lm", min.segment.length = Inf,
nudge_x = -.5, direction = "y") +
...

Related

How do I Facet_wrap without repeating coordinate values?

I am trying to create a plot which contains a discrete variable in one of the axis. I am furthermore trying to group these variables with respect to another variable and represent it in a graph using ggplot2. The code I have used is as follows:
size_vs_paper %>%
ggplot(aes(x=Reference,y=S_Max)) +
theme_classic()+
geom_segment(aes(xend =Reference,yend = S_Min),size=0.5) +
#geom_text(size = 5, vjust=-3) +
geom_point(aes(group = Environment), size = 3, shape = "|", color = "black", alpha = 0.7)+
geom_point(aes(y=S_Min, group = Environment), size = 3, shape = "|", color = "black", alpha = 0.7) +
geom_point(aes(y=S_Mean, group = Environment), size = 3, color = "black", alpha = 0.7) +
facet_wrap(Environment ~ ., ncol = 1) +
scale_colour_brewer(palette="Set2") +
scale_y_log10(breaks=c(0.01,1,10,100,1000, 5000, 10000)) +
theme(axis.text = element_text(size = 10),
legend.position = "none") +
coord_flip() +
labs(y = "Size (µm)")
This yields the graph as below:
As you can see, the Y axis representing the references repeats itself for all the facets. I am looking to facet them without the references repeating themselves. Any guidance will be appreciated!

adding custom ggplot legend to dashed lines and confidence bands

I'm having trouble setting a custom legend for confidence bands and dashed lines. This is my graph so far.
di<-matrix(ncol = 3,nrow = 5) %>% as.data.frame()
colnames(di)<-c('group','estimate','SE')
di<-di %>% mutate(group=1:5,
estimate=c(0.5,9.6,13,15,23.1),
SE=14)
ggplot(di, aes(x=group, y=estimate)) +
geom_point() +
geom_errorbar(width=.5, aes(ymin=estimate-(1.647*SE), ymax=estimate+(1.647*SE)), colour="black") +
xlab('Group') +
ylab('Treatment Effect') +
labs(title="GATE with confidence bands",
subtitle="Point estimates and confidence bands are derived using median of all splits") +
geom_hline(yintercept=c(7.83,22.55),
linetype="longdash",
col='darkred') +
geom_hline(yintercept=15.19,
linetype="longdash",
col='blue')
It looks like this:
However what I want it to look like is something like this, with the exact same legend:
Any advice on this?
This could be achieved like so:
As a general rule: If you want to have a legend you have to map something on aesthetics, e.g. move color=... into aes() for all four geoms
The desired color values can then be set via scale_color_manual
For the geom_hline we also have to pass yintercept as an aes() too. To this end these get something helper data frames with the desired values.
To fix the lines and shapes in the legend I make use of guide_legend's overide.aes to remove the undesired points in the legend as well as removing the line for the point. Additionally I set the number of rows for the legend to 2.
The labels and the order of the layers can be set via the labels and the breaks argument of scale_color_manual
Move the legend in the topleft and get rid of the background fill for the legend and the keys via theme options.
library(ggplot2)
di <- data.frame(
group = 1:5,
estimate = c(0.5, 9.6, 13, 15, 23.1),
SE = 14
)
labels <- c(point = "Point", error = "Error", blue = "Blue", darkred = "Red")
breaks <- c("blue", "darkred", "point", "error")
ggplot(di, aes(x = group, y = estimate)) +
geom_point(aes(color = "point"), size = 3) +
geom_errorbar(width = .5, aes(
ymin = estimate - (1.647 * SE),
ymax = estimate + (1.647 * SE),
color = "error"
)) +
scale_color_manual(values = c(
point = "black",
error = "black",
blue = "blue",
darkred = "darkred"
), labels = labels, breaks = breaks) +
labs(
title = "GATE with confidence bands",
subtitle = "Point estimates and confidence bands are derived using median of all splits",
x = "Group",
y = "Treatment Effect",
color = NULL, linetype = NULL, shape = NULL
) +
geom_hline(
data = data.frame(yintercept = c(7.83, 22.55)),
aes(yintercept = yintercept, color = "darkred"), linetype = "longdash"
) +
geom_hline(
data = data.frame(yintercept = 15.19),
aes(yintercept = yintercept, color = "blue"), linetype = "longdash"
) +
guides(color = guide_legend(override.aes = list(
shape = c(NA, NA, 16, NA),
linetype = c("longdash", "longdash", "blank", "solid")
), nrow = 2, byrow = TRUE)) +
theme(legend.position = c(0, 1),
legend.justification = c(0, 1),
legend.background = element_rect(fill = NA),
legend.key = element_rect(fill = NA))

Combine legend for fill and colour ggplot to give only single legend

I am plotting a smooth to my data using geom_smooth and using geom_ribbon to plot shaded confidence intervals for this smooth. No matter what I try I cannot get a single legend that represents both the smooth and the ribbon correctly, i.e I am wanting a single legend that has the correct colours and labels for both the smooth and the ribbon. I have tried using + guides(fill = FALSE), guides(colour = FALSE), I also read that giving both colour and fill the same label inside labs() should produce a single unified legend.
Any help would be much appreciated.
Note that I have also tried to reset the legend labels and colours using scale_colour_manual()
The below code produces the below figure. Note that there are two curves here that are essentially overlapping. The relabelling and setting couours has worked for the geom_smooth legend but not the geom_ribbon legend and I still have two legends showing which is not what I want.
ggplot(pred.dat, aes(x = age.x, y = fit, colour = tagged)) +
geom_smooth(size = 1.2) +
geom_ribbon(aes(ymin = lci, ymax = uci, fill = tagged), alpha = 0.2, colour = NA) +
theme_classic() +
labs(x = "Age (days since hatch)", y = "Body mass (g)", colour = "", fill = "") +
scale_colour_manual(labels = c("Untagged", "Tagged"), values = c("#3399FF", "#FF0033")) +
theme(axis.title.x = element_text(face = "bold", size = 14),
axis.title.y = element_text(face = "bold", size = 14),
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12),
legend.text = element_text(size = 12))
The problem is that you provide new labels for the color-aesthetic but not for the fill-aesthetic. Consequently ggplot shows two legends because the labels are different.
You can either also provide the same labels for the fill-aesthetic (code option #1 below) or you can set the labels for the levels of your grouping variable ("tagged") before calling ggplot (code option #2).
library(ggplot2)
#make some data
x = seq(0,2*pi, by = 0.01)
pred.dat <- data.frame(x = c(x,x),
y = c(sin(x), cos(x)) + rnorm(length(x) * 2, 0, 1),
tag = rep(0:1, each = length(x)))
pred.dat$lci <- c(sin(x), cos(x)) - 0.4
pred.dat$uci <- c(sin(x), cos(x)) + 0.4
#option 1: set labels within ggplot call
pred.dat$tagged <- as.factor(pred.dat$tag)
ggplot(pred.dat, aes(x = x, y = y, color = tagged, fill = tagged)) +
geom_smooth(size = 1.2) +
geom_ribbon(aes(ymin = lci, ymax = uci), alpha = 0.2, color = NA) +
scale_color_manual(labels = c("untagged", "tagged"), values = c("#F8766D", "#00BFC4")) +
scale_fill_manual(labels = c("untagged", "tagged"), values = c("#F8766D", "#00BFC4")) +
theme_classic() + theme(legend.title = element_blank())
#option 2: set labels before ggplot call
pred.dat$tagged <- factor(pred.dat$tag, levels = 0:1, labels = c("untagged", "tagged"))
ggplot(pred.dat, aes(x = x, y = y, color = tagged, fill = tagged)) +
geom_smooth(size = 1.2) +
geom_ribbon(aes(ymin = lci, ymax = uci), alpha = 0.2, color = NA) +
theme_classic() + theme(legend.title = element_blank())

Adding text outside the ggplot area

I am trying to make a combo chart using ggplot2. However i want to add a text box sort outside my plot body. I am unable to place it at the desired location
I have used grid pack to create grob and include that in annotation in the ggplot code. Additionally i have also put the same text in geom_text. How do i ensure the text comes say below the legend. Following is my code
m <- ggplot() +
geom_area(data= (ly_vol_ntwk %>%
mutate(Wk_end_d = as.factor(Wk_end_d))%>%
filter(!is.na(value_new))),
aes(x = Wk_end_d, y = value_new ,group = variable,fill=variable))+
geom_bar(data = (fcst_act_vol_ntwk %>%
mutate(Wk_end_d = as.factor(Wk_end_d))%>%
filter(!is.na(value_new))),
aes(x = Wk_end_d, y = value_new, group = variable, fill = variable),
stat = "identity",position = "dodge", width =0.5)+
geom_line(data = (var_vol_ntwk %>%
mutate(Wk_end_d = as.factor(Wk_end_d))%>%
filter(!is.na(value_new))),
aes(x = Wk_end_d, y = value_new,
group = variable, fill= variable), size = 0.8)+
scale_y_continuous(sec.axis = sec_axis(trans = ~./100000,
name = "Variance", breaks = waiver(),
labels=function(x) paste0(x,"%")))+
theme_set(theme_bw())+
theme(axis.text.x = element_text(angle=65, vjust=0.5,face = "plain"),
text = element_text(size=9), legend.position = "bottom", legend.title = element_blank())+
labs(title= "Inbound - Network", x= "Week end date", y = " ")+
scale_fill_manual(values = c("#C5E0B4","#7030A0", "#D9D9D9","#ED7D31","black"))+
geom_text(label = "LW Variance",
aes(x = 19, y = -1960000),
check_overlap = TRUE) #annotation_custom(grob = textGrob("LW Variance"), xmin = 18, xmax = 18, ymin = -1030000, ymax = -1030000)+ coord_cartesian(clip = 'off')
I need to get the text box with a border outside the area of the ggplot. Can you please help me?
You can place text below plot area with labs(caption = "text"), but you can't place captions on top of the plot. However, you could use subtitles labs(subtitle = "text") to produce a similar visual of captions on the top.
To further control the aspect of both options use theme(plot.caption = element_text(...), plot.subtitle = element_text(...)). Type ?element_text in your console to get all the options for text formatting.
For example:
library(ggplot2)
df <- data.frame(x = rnorm(50), y = rnorm(50))
ggplot(df, aes(x, y)) +
geom_point() +
labs(subtitle = "Your text here", caption = "Your text here") +
theme(plot.caption = element_text(colour = "red", hjust = 0, angle = 15),
plot.subtitle = element_text(size = 18, face = "bold", hjust = 0.8))
If you want it below your current legend, you can always add a dummy legend and put your text as its name. An example:
ggplot(mtcars, aes(mpg, wt, color = gear,fill = "a")) +
geom_point() +
scale_fill_discrete(name = "Your custom caption\ngoes here", labels = "") +
theme(legend.key = element_rect(fill = "white")) +
guides(color = guide_legend(order = 1),
fill = guide_legend(order = 2, override.aes = list(linetype = 0, shape=NA))) # setting the order parameter in guide_legend will help place it below your existing legend(s)

Modifying legend in ggplot2

I'm seeking some assistance with modifying the legend in my plot using the data below.
dput(df)
structure(list(Week.Number = 1:16, Dist.18 = c(5331.83038, 14084.08602,
12219.423585, 14406.407445, 5032.74848, 10820.094835, 16935.546075,
15387.590625, 16195.21247, 20012.09881, 14057.385255, 5127.14891,
16241.98523, 12793.21837, 10526.785375, 6014.43878), HIR.18 = c(1098.56001,
4093.010015, 4372.84498, 4074.22002, 709.70499, 2460.04999, 5037.77501,
5521.029965, 5463.410025, 6761.34502, 3953.20997, 1189.89, 3663.69006,
2333.005005, 2289.38001, 1069.740005), V6.18 = c(0, 40.77, 63.505,
112.63, 52.395, 56.795, 211.115, 75.52, 215.059995, 121.725,
57.64, 15.35, 140.34, 15.615, 85.66, 31.815), Dist.17 = c(11820.06249,
18123.592835, 14560.30914, 17193.56009, 7733.785765, 15536.659865,
8694.08218, 19569.060865, 14153.71578, 18498.63446, 16452.63166,
16820.32351, 9242.407875, 8857.62039, 2371.09375, 10340.258575
), HIR.17 = c(2693.425035, 4971.474985, 4521.895065, 5561.53997,
1759.31996, 3924.48, 1893.485, 5571.700035, 3239.94503, 4773.02004,
5927.174995, 4537.58996, 1618.49499, 2771.84002, 284.56, 2181.749995
), V6.17 = c(15.58, 38.355, 240.355, 354.059995, 1.76, 187.575,
93.495, 184.925, 88.27, 165.08, 231.075, 171.09, 32.55, 93.88,
0, 56.19)), .Names = c("Week.Number", "Dist.18", "HIR.18", "V6.18",
"Dist.17", "HIR.17", "V6.17"), row.names = c(NA, -16L), class = "data.frame")
This code generates the plot.
plot <- ggplot(df, aes(x = Week.Number, y = Dist.18, fill = "2018")) +
geom_col() +
geom_line(aes(x = Week.Number, y = Dist.17, fill = "2017"), size = 0.75) +
geom_point(aes(x = Week.Number, y = Dist.17), size = 0.75) +
scale_fill_manual("color", values = c("2017" = "black", "2018" = "blue")) +
scale_x_continuous(breaks = c(1:16)) +
ylab("Dist") +
theme_classic() +
theme(plot.title = element_text(face = "bold"),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold"))
I wish to change the title of the legend to "Season" and modify the key. I'm wondering if it's possible to have two different points in the key. For example, a solid blue square for the label 2018 and a black line for 2017, representing each geom in the plot.
Also, i used fill = in the aes() argument to generate a legend in the first instance. This seems to work, but not sure if it's best practice or not.
Hope I've provided enough information. Any help will be greatly appreciated. Thank you.
As per my comment above, one legend is created for each 'aesthetics' - you have currently only the fill aesthetics. If you want more than one legend, you need to specify several aesthetics, here e.g. linetype or color.
There are some problems with your code, though.
First, in order to make full use of ggplot's functionality with the aesthetics and grouping, I would recommend putting your data in a long format - currently it's in a wide format. E.g., it might make sense to group by years - you could achieve that to put all values which belong to one measurememt into one column, and have a column specifying the year, and then specify the aes for this 'year- column'.
Furthermore, See comments below
ggplot(df) +
# avoid specifying your `aes` in the ggplot main call -
# specially if you have several plots following.
# Some people say it's even better to leave it completely empty.
geom_col(aes(x = Week.Number, y = Dist.18, fill = "2018")) +
# now here you are currently not really making use of the aes-functionality,
# because you are only creating an aesthetic for one value, i.e. '2018'
geom_line(aes(x = Week.Number, y = Dist.17, color = "2017"), size = 0.75) +
# Here I have changed fill to color
geom_point(aes(x = Week.Number, y = Dist.17), size = 0.75) +
scale_fill_manual("your title", values = c("2017" = "black", "2018" = "blue")) +
# this is to show you that you actually already know
# how to change your legend title - see the graph :)
scale_x_continuous(breaks = c(1:16)) +
ylab ("Dist") +
theme_classic()
I guess it would be nice to have one title for both legends:
ggplot(df, aes(x = Week.Number)) +
geom_col(aes(y = Dist.18, fill = "2018")) +
geom_line(aes(y = Dist.17, col = "2017"), size = 0.75) +
geom_point(aes(y = Dist.17, col = "2017")) +
scale_colour_manual("Season", values = c("2017" = "black")) +
scale_fill_manual("", values = c("2018" = "blue")) +
scale_x_continuous(breaks = c(1:16)) +
ylab ("Dist") +
theme_classic() +
theme(plot.title = element_text(face = "bold"),
axis.title.x = element_text(face = "bold"),
axis.title.y = element_text(face = "bold")) +
theme(legend.margin = margin(-0.8, 0, 0, 0, unit = "cm"))
If you do not want to have point in the legend, just remove col = "2017" from geom_point and you get:
The trick is to remove space between two legends with legend.margin argument in theme.

Resources