Here's an example from the ggplot2 homepage: https://ggplot2.tidyverse.org/reference/geom_errorbarh.html as others have mentioned in the comments, you have to calculate SE on your own and append this information to the data.frame
df <- data.frame(
trt = factor(c(1, 1, 2, 2)),
resp = c(1, 5, 3, 4),
group = factor(c(1, 2, 1, 2)),
se = c(0.1, 0.3, 0.3, 0.2)
)
df2 <- df[c(1,3),]
p <- ggplot(df, aes(fill=group, y=resp, x=trt))
p + geom_bar(position="dodge", stat="identity")
dodge <- position_dodge(width=0.9)
p + geom_bar(position=dodge, stat="identity") + geom_errorbar(aes(ymax = resp + se, ymin=resp - se), position=dodge, width=0.25)
As a pointer, SE @ 95% CI usually looks something like this:
df$se <- 1.96*(sd(your_data, na.rm=T)/sqrt(your_n))
Your upper and lower CI bounds will just be df$se +/- the response (as shown in the aes() for geom_errorbar(), above)
I agree with EdM's point that "bar plots simply have too much ink for the information conveyed." Here's a ggplot2 version of his answer:
library(ggplot2)
df <- data.frame(years=c(1991, 1993, 1997, 2001, 2005, 2007, 2011, 2015),
freq=c(43.20, 52.13, 47.93, 46.29, 40.57, 53.88, 48.92, 50.92))
p <- (ggplot(df, aes(x=years, y=freq)) +
geom_line(size=1.25, color="#999999") + geom_point(size=3.5, color="black") +
theme_bw() +
theme(panel.border=element_blank(), panel.grid.minor=element_blank(),
axis.title.y=element_text(vjust=1.25)) +
scale_x_continuous("", breaks=seq(1990, 2015, 5), minor_breaks=NULL) +
scale_y_continuous("percentage turnout", limits=c(36, 59),
breaks=seq(40, 55, 5), minor_breaks=NULL))
p
ggsave("percentage_turnout_over_time.png", p, width=10, height=8)
Which produces this:
![ggplot2 graph](https://i.stack.imgur.com/tg2Er.png)
Edit: here's a version with numbers on the graph:
p <- (ggplot(df, aes(x=years, y=freq, label=freq)) +
geom_line(size=1.25, color="#999999") + geom_point(size=3.5, color="black") +
geom_text(vjust=c(2, -1, -1.5*sign(diff(diff(df$freq))) + 0.5)) +
theme_bw() +
theme(panel.border=element_blank(), panel.grid.minor=element_blank(),
axis.title.y=element_text(vjust=1.25)) +
scale_x_continuous("", breaks=seq(1990, 2015, 5), minor_breaks=NULL) +
scale_y_continuous("percentage turnout", limits=c(36, 59),
breaks=seq(40, 55, 5), minor_breaks=NULL))
p
ggsave("percentage_turnout_over_time_with_text.png", p, width=10, height=8)
![ggplot2 graph second version](https://i.stack.imgur.com/o6kTd.png)
Nick Cox's comment under the original post is convincing:
I see no harm in showing numbers too. People often want to read
numbers off graphs just as they (should) want to read numbers off
tables. Also, offering graph PLUS table in a paper would often be
rejected by reviewers as too much space devoted to the same
information, so hybridising graph and table is perfectly defensible.
Best Answer
Since you didn't provide a reproducible dataset, I will use
ToothGrowth
. First, create a new data frame containing the mean values for each group.This data frame can be used for
ggplot
. You can place text withgeom_text
.