20

Consider for example this plot using the data mtcars and the function coord_flip

library(ggplot2)
library(Hmisc)

ggplot(mtcars,aes(x=gear,y=cyl)) + stat_summary(aes(color=as.factor(rep(1:2,16))),
fun.data=mean_cl_boot, position=position_dodge(0.4)) + coord_flip()

enter image description here

The fact that error bars are horizontal on the graph but vertical in the legend bothers me :) How can I rotate these symbols?

SymbolixAU
  • 25,502
  • 4
  • 67
  • 139
Remi.b
  • 17,389
  • 28
  • 87
  • 168
  • 1
    Raise a GitHub issue and wait till Hadley gets another intern. Better, write a pull request. – alistaire Oct 25 '16 at 00:12
  • 2
    perhaps useful http://stackoverflow.com/questions/35703983/how-to-change-angle-of-line-in-customized-legend-in-ggplot2 – user20650 Oct 25 '16 at 02:10
  • The symbol should flip by default when we use coord_flip(), but anyway this function should be easily editable by users. – LuizZ Jul 03 '21 at 21:06

5 Answers5

12

Tweak the legend key

GeomPointrange$draw_key <-  function (data, params, size)     {

         draw_key_vpath <- function (data, params, size) {
           # only need to change the x&y coords so that the line is horizontal
           # originally, the vertical line was `0.5, 0.1, 0.5, 0.9`
              segmentsGrob(0.1, 0.5, 0.9, 0.5, 
              gp = gpar(col = alpha(data$colour, data$alpha), 
              lwd = data$size * .pt, lty = data$linetype, 
              lineend = "butt"), arrow = params$arrow)
              }

    grobTree(draw_key_vpath(data, params, size), 
             draw_key_point(transform(data, size = data$size * 4), params))
}

Then plot

 ggplot(mtcars,aes(x=gear,y=cyl)) + 
    stat_summary(aes(color=as.factor(rep(1:2,16))),
                  fun.data=mean_cl_boot, position=position_dodge(0.4)) + 
    coord_flip()
user20650
  • 24,654
  • 5
  • 56
  • 91
7

I'm not coming up with an answer that works within the normal ggplot2 workflow, so for now, here's a hacky answer. Turn off the stat_summary legend. Then, add point and line geoms with data that is outside the range of the actual data you want to plot. This will create the point and horizontal line legend that you want. Then set the plot axis limits to include only the range of your real data, so that the fake data points are not visible.

ggplot(mtcars, aes(x=gear, y=cyl, color=as.factor(rep(1:2,16)))) + 
  stat_summary(fun.data=mean_cl_boot, position=position_dodge(0.4), show.legend=FALSE) + 
  geom_line(aes(y=cyl-100)) +
  geom_point(aes(y=cyl-100), size=2.5) +
  coord_flip(ylim=range(mtcars$cyl)) 

enter image description here

Another option would be to rotate the legend-key grobs by 90 degrees using grid functions, but I'll leave that for someone who's more skilled with grid than I am.

eipi10
  • 91,525
  • 24
  • 209
  • 285
5

updated answer

ggplot2 3.3.0 fixed this. Using the geom_pointrange() function will render horizontal error bars in the legend when using the xmin and xmax args:

library(ggplot2)
library(dplyr)

df <- mtcars |> 
  mutate(gear = as.factor(gear),
         am = as.factor(am)) |> 
  group_by(gear, am) |> 
  summarise(cyl_mean = mean(cyl),
            cyl_upr =  mean(cyl) + sd(cyl)/sqrt(length(cyl)),
            cyl_lwr = mean(cyl) - sd(cyl)/sqrt(length(cyl)))

ggplot(df, 
       aes(x=cyl_mean, y=gear, 
           color = am, group = am)) +
  geom_pointrange(aes(xmin = cyl_lwr,
                      xmax = cyl_upr),
                  position = position_dodge(0.25))

Created on 2023-05-16 with reprex v2.0.2

old answer

The ggstance package provides an easy to implement solution here:

library(ggplot2)
library(ggstance)

ggplot(mtcars,aes(x=cyl,y=gear)) + stat_summaryh(aes(color=as.factor(rep(1:2,16))),
                                                fun.data=mean_cl_boot_h, position = position_dodgev(height = 0.4))

errorbars

or as a geom:

df <- data.frame(x = 1:3, y = 1:3)
ggplot(df, aes(x, y, colour = factor(x))) +
     geom_pointrangeh(aes(xmin = x - 1, xmax = x + 1))
mpschramm
  • 520
  • 6
  • 12
3

Following up @eipi10's suggestion to use grid functions to edit the grobs - the relevant grobs are segments. There are two possibilities: 1) rotate the segment grobs; or 2) edit the x and y coordinates of the endpoints of the segment grobs.

library(ggplot2)
library(Hmisc)

library(grid)

p = ggplot(mtcars,aes(x=gear,y=cyl)) + 
    stat_summary(aes(color=as.factor(rep(1:2,16))),
                  fun.data=mean_cl_boot, position=position_dodge(0.4)) + 
    coord_flip()

g = ggplotGrob(p)

# Get names of segment grobs
grid.ls(grid.force(g))$name   # "GRID.segments"

# Check the structure of the segment grobs
str(getGrob(grid.force(g), gPath("GRID.segments"), grep = TRUE, global = TRUE))

# Edit the segment grobs using the editGrob() function
# 1) Rotate the segments
    g <- editGrob(grid.force(g), gPath("GRID.segments"), grep = TRUE, global = TRUE,
        vp = viewport(angle = 90)) 

# 2) set end points of segments
#    g <- editGrob(grid.force(g), gPath("GRID.segments"), grep = TRUE, global = TRUE,  
#         x0 = unit(0.1, "npc"), y0 = unit(0.5, "npc"), x1 = unit(0.9, "npc"), y1 = unit(0.5, "npc"))

# Draw it
grid.newpage()
grid.draw(g)
Sandy Muspratt
  • 31,719
  • 12
  • 116
  • 122
1

Edited from: https://gist.github.com/grantmcdermott/d86af2b8f21f4082595c0e717eea5a90

The main point is to use geom_pointrangeh from ggstance and remember to specify aes w.r.t. x-axis.

library(tidyverse)
library(broom)
library(hrbrthemes) 
library('ggstance')
library('jtools')

df = 
  mtcars %>%
  mutate(vs = factor(vs), am = factor(am))

fit1 = lm(mpg ~ vs * am * wt, data = df) 
fit1_coefs = tidy(fit1, conf.int = T) 

fit2 = lm(mpg ~ vs / am / wt, data = df)
fit2_coefs = tidy(fit2, conf.int = T) 


bind_rows(
  fit1_coefs %>% mutate(model = "Model 1"),
  fit2_coefs %>% mutate(model = "Model 2")
) %>%
  filter(grepl("wt", term)) %>%
  ## Optional regexp work to make plot look nicier  
  mutate(
    am = ifelse(grepl("am1", term), "Automatic", "Manual"),
    vs = ifelse(grepl("vs1", term), "V-shaped", "Straight"),
    x_lab = paste(am, vs, sep="\n")
  ) %>%
  ggplot(aes(col = model,y=x_lab, x=estimate, xmin=conf.low, xmax=conf.high)) +
  geom_pointrangeh(position = position_dodge(width = 0.5)) +
  guides(color = guide_legend(reverse = TRUE)) +
  geom_vline(xintercept = 0, col = "black",lty=4) +
    labs(x = NULL, y = NULL,title = "Title") +
  theme_nice() +
  theme(plot.title = element_text(hjust = 0.5))

enter image description here

Samuel Saari
  • 1,023
  • 8
  • 13