Search code examples
rggplot2errorbar

Cannot change width of position_dodge using geom_line


I have yield data over two years that I am trying to graph with standard error bars but I cannot get position_dodge to work properly.

Here is my data:

structure(list(Year = c(2021, 2021, 2021, 2021, 2021, 2021, 2021, 
2021, 2021, 2021, 2021, 2021, 2021, 2021, 2022, 2022, 2022, 2022, 
2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 
2022, 2022, 2022), Date = structure(c(1616198400, 1616457600, 
1617062400, 1617580800, 1617840000, 1618272000, 1620086400, 1616198400, 
1616457600, 1617062400, 1617580800, 1617840000, 1618272000, 1620086400, 
1647561600, 1647820800, 1648166400, 1648771200, 1649203200, 1649721600, 
1650240000, 1650585600, 1651017600, 1647561600, 1647820800, 1648166400, 
1648771200, 1649203200, 1649721600, 1650240000, 1650585600, 1651017600
), class = c("POSIXct", "POSIXt"), tzone = "UTC"), Treatment = c("Ash", 
"Ash", "Ash", "Ash", "Ash", "Ash", "Ash", "Control", "Control", 
"Control", "Control", "Control", "Control", "Control", "Ash", 
"Ash", "Ash", "Ash", "Ash", "Ash", "Ash", "Ash", "Ash", "Control", 
"Control", "Control", "Control", "Control", "Control", "Control", 
"Control", "Control"), Yield = c(2.51583333333333, 13.1966666666667, 
11.7541666666667, 19.1791666666667, 4.93166666666667, 1.6425, 
0.534166666666667, 1.57, 8.6625, 4.58333333333333, 8.72916666666667, 
1.77291666666667, 0.695833333333333, 0.822083333333333, 1.53083333333333, 
3.52, 7.96083333333333, 3.8375, 15.8908333333333, 9.71916666666667, 
5.80833333333333, 6.12, 1.4825, 1.87916666666667, 3.36333333333333, 
6.77666666666667, 3.95166666666667, 15.1875, 7.9, 5.4625, 5.85083333333333, 
1.60416666666667), se = c(0.731662136729264, 0.223853983794361, 
1.4186561787605, 1.31766329273844, 1.27935913071879, 0.823042520103907, 
1.56237070172093, 1.59950819066993, 1.1341194911481, 0.420558997740874, 
0.519476900352653, 0.244724515289822, 0.32226285227993, 0.789295681991933, 
0.34560762989029, 0.359135026378265, 0.681862422807097, 0.648019515855266, 
1.00956445629496, 0.70330101893309, 0.481196622384176, 0.404358514292463, 
0.64485076771782, 1.00096478081111, 1.24107325152731, 0.760189370346547, 
1.02632754631415, 0.929991955814021, 1.15488685002629, 1.30922800427264, 
0.451898562296961, 0.536789641001825)), row.names = c(NA, -32L
), class = c("tbl_df", "tbl", "data.frame"))

Here is my code:

yield %>% 
  ggplot(aes(x = Date, y = Yield, color = Treatment, group = Treatment)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  geom_errorbar(aes(ymin=Yield-se, ymax=Yield+se), color = "black", position = "dodge") +
  theme(panel.background = element_blank(),
        panel.border = element_rect(colour = "black", fill = NA)) +
  theme(text = element_text(size = 13)) +
  facet_grid(. ~ Year, scales = "free_x") +
  labs(y = "Average Yield (L)")

I have tried setting both e.g.

pd <- position_dodge(x)
position = pd

as well as

position = position_dodge(x)

with various different width specifications (e.g. x = 0.1, 1, 50, 500, 365*0.5 or 30 * 0.9 since my x-axis is date) and adding these arguments to the geom_line, geom_point, and geom_errorbar lines but none have worked so far. The only way to get the error bars to move is by one of two options:

position = position_dodge2()
position = "dodge"

But I cannot change how far the errorbars move because the width specifications above do not make a difference and if I add the argument

width = x

to geom_errorbar it removes the top and bottom lines from the errorbars.

I have also tried adding group = treatment to geom_line, geom_point, and geom_errorbar but this hasn't changed anything.

What I am ultimately looking for is to keep the errorbars that are not overlapping in the same position and have the errorbars that are overlapping shift only slightly (not as much in the attached graph). enter image description here Any idea what I'm missing here?

I have tried solutions in the following links with no luck: Dodge error bars and points to avoid overlap & using position_dodge with geom_line


Solution

  • As I already said in my comment to achieve your final goal is IMHO not possible using position="dodge", i.e. you dodge all error bars by the same amount or none. Instead I would suggest to do the dodging manually via the x positions. To this end first determine the dates where the error bars overlap. For these dates you could then add or subtract the amount by which you want the errors to be dodged. In my code below I dodge by one day:

    library(ggplot2)
    library(dplyr)
    library(tidyr)
    
    dodge_width <- 1 # days
    yield <- yield |>
      mutate(lo = Yield - se, hi = Yield + se) |>
      select(-Yield, -se) |>
      pivot_wider(names_from = Treatment, values_from = c(lo, hi)) |>
      mutate(is_dodge = (lo_Ash >= lo_Control & lo_Ash <= hi_Control) |
        (hi_Ash >= lo_Control & hi_Ash <= hi_Control) |
        (lo_Control >= lo_Ash & lo_Control <= hi_Ash) |
        (hi_Control >= lo_Ash & hi_Control <= hi_Ash)) |>
      select(Year, Date, is_dodge) |>
      right_join(yield) |>
      mutate(dodge = Date + case_when(
        is_dodge & Treatment == "Ash" ~ -lubridate::days(dodge_width),
        is_dodge & !Treatment == "Ash" ~ lubridate::days(dodge_width),
        TRUE ~ lubridate::days(0)
      ))
    #> Joining, by = c("Year", "Date")
    
    ggplot(yield, aes(x = Date, y = Yield, color = Treatment, group = Treatment)) +
      geom_line(size = 1) +
      geom_point(size = 2) +
      geom_errorbar(aes(x = dodge, ymin = Yield - se, ymax = Yield + se), color = "black") +
      theme(
        panel.background = element_blank(),
        panel.border = element_rect(colour = "black", fill = NA)
      ) +
      theme(text = element_text(size = 13)) +
      facet_grid(. ~ Year, scales = "free_x") +
      labs(y = "Average Yield (L)")