How to make half-wiskers in a ggplot2 line graph? - r

I make very slow progress in R but now I'm able to do some stuff.
Right now I'm plotting the effects of 4 treatments on plant growth in one graph. As you can see the errorbars overlap which is why I made them different colors. I think in order to make the graph clearer it's better to use the lower errorbars as "half wiskers" for the lower 2 lines, and the upper errorbars for the top two lines (like I have now), see the attached image for reference
Is that doable with the way my script is set up now?
Here is part of my script of the plot, I have a lot more but this is where I specify the plot itself (leaving out the aesthetics and stuff), thanks in advance:
"soda1" is my altered dataframe, setup in a clear way, "sdtv" are my standard deviations for each timepoint/treatment, "oppervlak" is my y variable and "Measuring Date" is my x variable. "Tray ID" is the treatment, so my grouping variable.
p <- ggplot(soda1, aes(x=reorder(`Measuring Date`, oppervlak), y=`oppervlak`, group=`Tray ID`, fill=`Tray ID`, colour = `Tray ID` )) +
scale_fill_brewer(palette = "Spectral") +
geom_errorbar(data=soda1, mapping=aes(ymin=oppervlak, ymax=oppervlak+sdtv, group=`Tray ID`), width=0.1) +
geom_line(aes(linetype=`Tray ID`)) +
geom_point(mapping=aes(x=`Measuring Date`, y=oppervlak, shape=`Tray ID`))
print(p)

Showing only one side of errorbars can hide an overlap in the uncertainty between the distribution of two or more variables or measurements.
Instead of hiding this overlap, you could adjust the position of your errorbars horizontally very easily by adding position=position_dodge(width=) to your call to geom_errorbar().
For example:
library(ggplot2)
# some random data with two factors
df <- data.frame(a=rep(1:10, times=2),
b=runif(20),
treat=as.factor(rep(c(0,1), each=10)),
errormax=runif(20),
errormin=runif(20))
# plotting both sides of the errorbars, but dodging them horizontally
p <- ggplot(data=df, aes(x=a, y=b, colour=treat)) +
geom_line() +
geom_errorbar(data=df, aes(ymin=b-errormin, ymax=b+errormax),
position=position_dodge(width=0.25))

Related

How can I ensure consistent axis lengths between plots with discrete variables in ggplot2?

I've been trying to standardise multiple bar plots so that the bars are all identical in width regardless of the number of bars. Note that this is over multiple distinct plots - faceting is not an option. It's easy enough to scale the plot area so that, for instance, a plot with 6 bars is 1.5* the width of a plot with 4 bars. This would work perfectly, except that each plot has an expanded x axis by default, which I would like to keep.
"The defaults are to expand the scale by 5% on each side for continuous variables, and by 0.6 units on each side for discrete variables."
https://ggplot2.tidyverse.org/reference/scale_discrete.html
My problem is that I can't for the life of me work out what '0.6 units' actually means. I've manually measured the distance between the bars and the y axis in various design tools and gotten inconsistent answers, so I can't factor '0.6 units' into my calculations when working out what size the panel windows should be. Additionally I can't find any answers on how many 'units' long a discrete x axis is - I assumed at first it would be 1 unit per category but that doesn't fit with the visuals at all. I've included an image that hopefully shows what I mean - the two graphs
In this image, the top graph has a plot area exactly 1.5* that of the bottom graph. Seeing as it has 6 bars compared with 4, that would mean each bar is the same width, except that that extra space between the axis and the first bar messes this up. Setting expand = expansion(add = c(0, 0)) clears this up but results in not-so-pretty graphs. What I'd like is for the bars to be identical in width between the two plots, accounting for this extra space. I'm specifically looking for a general solution that I can use for future plots, not for the individual solution for this sample. As such, what I'd really like to know is how many 'units' long are these two x axes? Many thanks for any and all help!
Instead of using expansion for the axis, I would probably use the fact that categorical variables are actually plotted on the positive integers on Cartesian co-ordinates. This means that, provided you know the maximum number of columns you are going to use in your plots, you can set this as the range in coord_cartesian. There is a little arithmetic involved to keep the bars centred, but it should give consistent results.
We start with some reproducible data:
library(ggplot2)
set.seed(1)
df <- data.frame(group = letters[1:6], value = 100 * runif(6))
Now we set the value for the maximum number of bars we will need:
MAX_BARS <- 6
And the only thing "funny" about the plot code is the calculation of the x axis limits in coord_cartesian:
ggplot(df, aes(group, value)) +
geom_col() +
coord_cartesian(xlim = c(1 -(MAX_BARS - length(unique(df$group)))/2,
MAX_BARS - (MAX_BARS - length(unique(df$group)))/2))
Now let us remove one factor level and run the exact same plot code:
df <- df[-1,]
ggplot(df, aes(group, value)) +
geom_col() +
coord_cartesian(xlim = c(1 -(MAX_BARS - length(unique(df$group)))/2,
MAX_BARS - (MAX_BARS - length(unique(df$group)))/2))
And again:
df <- df[-1,]
ggplot(df, aes(group, value)) +
geom_col() +
coord_cartesian(xlim = c(1 -(MAX_BARS - length(unique(df$group)))/2,
MAX_BARS - (MAX_BARS - length(unique(df$group)))/2))
And again:
df <- df[-1,]
ggplot(df, aes(group, value)) +
geom_col() +
coord_cartesian(xlim = c(1 -(MAX_BARS - length(unique(df$group)))/2,
MAX_BARS - (MAX_BARS - length(unique(df$group)))/2))
You will see the bars remain constant width and centralized, yet the panel size remains fixed.
Created on 2021-11-06 by the reprex package (v2.0.0)

ggplot: Adding labels to lines, not endpoints

Looking for some gglot help here, for a pretty non-standard plot type. Here is code for one sample graph, but the final product will have dozens like this.
library(ggplot2)
library(data.table)
SOURCE <- c('SOURCE.1','SOURCE.1','SOURCE.2','SOURCE.2')
USAGE <- rep(c('USAGE.1','USAGE.2'),2)
RATIO <- c(0.95,0.05,0.75,0.25)
x <- data.table(SOURCE,USAGE,RATIO)
ggplot(x, aes(x=SOURCE,y=RATIO,group=USAGE)) +
geom_point() +
geom_line() +
geom_label(aes(label=USAGE))
This produces a graph with two lines, as desired. But the label geom adds text to the endpoints. What we want is the text to label the line (appearing once per line, around the middle). The endpoints will always have the same labels so it just creates redundancy and clutter (each graph will have different labels). See the attached file, mocked up in a paint programme (ignore the font and size):
I know we could use geom_line(aes(linetype=USAGE)), but we prefer not to rely on legends due to the sheer number of graphs required and because each graph is quite minimal as the vast majority will have just the two lines and the most extreme cases will only have four.
(Use of stacked bars deliberately avoided.)
You can achieve this with annotate and can move the label around by changing the x and y values.
library(ggplot2)
#library(data.table)
SOURCE<-c('SOURCE.1','SOURCE.1','SOURCE.2','SOURCE.2')
USAGE<-rep(c('USAGE.1','USAGE.2'),2)
RATIO<-c(0.95,0.05,0.75,0.25)
#x<-data.table(SOURCE,USAGE,RATIO)
df <- data.frame(SOURCE, USAGE, RATIO)
ggplot(df, aes(x=SOURCE,y=RATIO,group=USAGE)) +
geom_point() +
geom_line() +
#geom_label(aes(label=USAGE))+
annotate('text', x=1.5, y=1, label = USAGE[1])+
annotate('text', x=1.5, y=0.25, label = USAGE[2])

Can the minimum y-value be adjusted when using scales = "free" in ggplot?

Using the following data set:
day <- gl(8,1,48,labels=c("Mon","Tues","Wed","Thurs","Fri","Sat","Sun","Avg"))
day <- factor(day, level=c("Mon","Tues","Wed","Thurs","Fri","Sat","Sun","Avg"))
month<-gl(3,8,48,labels=c("Jan","Mar","Apr"))
month<-factor(month,level=c("Jan","Mar","Apr"))
snow<-gl(2,24,48,labels=c("Y","N"))
snow<-factor(snow,levels=c("Y","N"))
count <- c(.94,.95,.96,.98,.93,.94,.99,.9557143,.82,.84,.83,.86,.91,.89,.93,.8685714,1.07,.99,.86,1.03,.81,.92,.88,.9371429,.94,.95,.96,.98,.93,.94,.99,.9557143,.82,.84,.83,.86,.91,.89,.93,.8685714,1.07,.99,.86,1.03,.81,.92,.88,.9371429)
d <- data.frame(day=day,count=count,month=month,snow=snow)
I like the y-scale in this graph, but not the bars:
ggplot()+
geom_line(data=d[d$day!="Avg",],aes(x=day, y=count, group=month, colour=month))+
geom_bar(data=d[d$day=="Avg",],aes(x=day, y=count, fill=month),position="dodge", group=month)+
scale_x_discrete(limits=levels(d$day))+
facet_wrap(~snow,ncol=1,scales="free")+
scale_y_continuous(labels = percent_format())
I like the points, but not the scale:
ggplot(data=d[d$day=="Avg",],aes(x=day, y=count, fill=month,group=month,label=month),show_guide=F)+
facet_wrap(~snow,ncol=1,scales="free")+
geom_line(data=d[d$day!="Avg",],aes(x=day, y=count, group=month, colour=month), show_guide=F)+
scale_x_discrete(limits=levels(d$day))+
scale_y_continuous(labels = percent_format())+
geom_point(aes(colour = month),size = 4,position=position_dodge(width=1.2))
How to combine the desirable qualities in the above graphs?
Essentially, I'm asking: How can I graph the points with a varied y-max while setting the y-min to zero?
Note: The solution that I'm aiming to find will apply to about 27 graphs built from one dataframe. So I'll vote up those solutions that avoid alterations to individual graphs. I'm hoping for a solution that applies to all the facet wrapped graphs.
Minor Questions (possibly for a separate post):
- How can I add a legend to each of the facet wrapped graphs? How
can I change the title of the legend to read "Weekly Average"? How
can the shape/color of the lines/points be varied and then reported
in one single legend?
there's expand_limits(y=0), which essentially adds a dummy layer with invisible geom_blank only to stretch the scales.

Grouped geom_boxplot from calculated values with mean

I created some grouped boxplots, basically for each dimension on the x axis I am showing various groups. Because my dataset is quite large, I had to precalculate the values for the boxes as ggplot did not have enough memory (I used ddply and did it in pieces).
I believe this is beter than just bar charts of the averages as it shows some of the variability.
I want 2 modifications, one was to not show the whisker lines, and I have done that by setting ymin=lower and ymax=upper.
I also wanted to add the means as well, but they show all in the center of each X category, and of course I want them each aligned with its box.
to make it easier on anyone helping, I recreated the same chart using mtcars - I tried position = "dodge" and "identity" with no change
Anyone knows how to do this? I searched and did not find a way. I am also attaching a picture of my latest chart. Code is below
data(mtcars)
data <- as.data.frame(mtcars)
data$cyl <- factor(data$cyl)
data$gear <- factor(data$gear)
summ <- ddply(data, .(cyl, gear),summarize, lower=quantile(mpg,probs=0.25,na.rm=T), middle=quantile(mpg,probs=.5,na.rm=T),upper=quantile(mpg,probs=.75,na.rm=T),avg=mean(mpg,na.rm=T))
p2 <- ggplot(summ, aes(x = cyl, lower = lower, middle = middle, upper = upper,fill=gear,ymin=lower,ymax=upper))+geom_boxplot(stat = "identity")
p2 <- p2 + geom_point(aes(x = cyl, y=avg, color=gear),color="red",position="dodge")
p2
The problem is that the width of the points is not the same as the width of the box plots. In that case you need to tell position_dodge what width do use. ?position_dodge gives a simple example of this using points and error bars, but the principle is the same for points and box plots. In your example, replacing position="dodge" with position=position_dodge(width=0.9) will dodge the points by the same amount as the box plots.

How to control ylim for a faceted plot with different scales in ggplot2?

In the following example, how do I set separate ylims for each of my facets?
qplot(x, value, data=df, geom=c("smooth")) + facet_grid(variable ~ ., scale="free_y")
In each of the facets, the y-axis takes a different range of values and I would like to different ylims for each of the facets.
The defaults ylims are too long for the trend that I want to see.
This was brought up on the ggplot2 mailing list a short while ago. What you are asking for is currently not possible but I think it is in progress.
As far as I know this has not been implemented in ggplot2, yet. However a workaround - that will give you ylims that exceed what ggplot provides automatically - is to add "artificial data". To reduce the ylims simply remove the data you don't want plot (see at the and for an example).
Here is an example:
Let's just set up some dummy data that you want to plot
df <- data.frame(x=rep(seq(1,2,.1),4),f1=factor(rep(c("a","b"),each=22)),f2=factor(rep(c("x","y"),22)))
df <- within(df,y <- x^2)
Which we could plot using line graphs
p <- ggplot(df,aes(x,y))+geom_line()+facet_grid(f1~f2,scales="free_y")
print(p)
Assume we want to let y start at -10 in first row and 0 in the second row, so we add a point at (0,-10) to the upper left plot and at (0,0) ot the lower left plot:
ylim <- data.frame(x=rep(0,2),y=c(-10,0),f1=factor(c("a","b")),f2=factor(c("x","y")))
dfy <- rbind(df,ylim)
Now by limiting the x-scale between 1 and 2 those added points are not plotted (a warning is given):
p <- ggplot(dfy,aes(x,y))+geom_line()+facet_grid(f1~f2,scales="free_y")+xlim(c(1,2))
print(p)
Same would work for extending the margin above by adding points with higher y values at x values that lie outside the range of xlim.
This will not work if you want to reduce the ylim, in which case subsetting your data would be a solution, for example to limit the upper row between -10 and 1.5 you could use:
p <- ggplot(dfy,aes(x,y))+geom_line(subset=.(y < 1.5 | f1 != "a"))+facet_grid(f1~f2,scales="free_y")+xlim(c(1,2))
print(p)
There are actually two packages that solve that problem now:
https://github.com/zeehio/facetscales, and https://cran.r-project.org/package=ggh4x.
I would recommend using ggh4x because it has very useful tools, such as facet grid multiple layers (having 2 variables defining the rows or columns), scaling the x and y-axis as you wish in each facet, and also having multiple fill and colour scales.
For your problems the solution would be like this:
library(ggh4x)
scales <- list(
# Here you have to specify all the scales, one for each facet row in your case
scale_y_continuous(limits = c(2,10),
scale_y_continuous(breaks = c(3, 4))
)
qplot(x, value, data=df, geom=c("smooth")) +
facet_grid(variable ~ ., scale="free_y") +
facetted_pos_scales(y = scales)
I have one example of function facet_wrap
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(vars(class), scales = "free",
nrow=2,ncol=4)
Above code generates plot as:
my level too low to upload an image, click here to see plot

Resources