Combined graph: line and histogram with ggplot2

Hello,

I have this dataset in Excel with which I create this chart

mois <- c(1:12)
A2022 <- c(43,43.2,43.5,43,42.8,42.7,42.2,42,42.6,42.7,42.9,43)
A2021 <- c(42.8,43,43.3,43.1,43,43.1,42.8,42.5,42.5,42.6,42.6,42.5)

df <- tibble(mois,A2022,A2021)
df <- df %>% 
  mutate(ecart=A2022-A2021)

I'm trying to do the same thing with ggplot but unfortunately I can't :frowning:

This is R code

ratio <- max(df$A2022/max(df$ecart))

df %>% ggplot()+
  geom_bar(aes(x=mois, y = ecart),stat="identity")+
  geom_line(aes(x=mois, y= A2022/ratio),stat="identity", group = 1)+
  scale_y_continuous("Ecart", sec.axis = sec_axis(~ . * ratio, name = "Moyenne")) 

thanks for your help !

I don't thing you want to use a ratio to scale the data but, rather, an offset.

library(tidyverse)
mois <- c(1:12)
A2022 <- c(43,43.2,43.5,43,42.8,42.7,42.2,42,42.6,42.7,42.9,43)
A2021 <- c(42.8,43,43.3,43.1,43,43.1,42.8,42.5,42.5,42.6,42.6,42.5)

df <- tibble(mois,A2022,A2021)
df <- df %>% 
  mutate(ecart=A2022-A2021)

MeanA2022 <- mean(df$A2022)
df %>% ggplot()+
  geom_bar(aes(x=mois, y = ecart),stat="identity")+
  geom_line(aes(x=mois, y= A2022 - MeanA2022),stat="identity", group = 1)+
  scale_y_continuous("Ecart", sec.axis = sec_axis(~ . + MeanA2022, name = "Moyenne")) 

Created on 2022-11-22 with reprex v2.0.2

Is this what you want?

library(tidyverse)

mois <- c(1:12)
A2022 <- c(43,43.2,43.5,43,42.8,42.7,42.2,42,42.6,42.7,42.9,43)
A2021 <- c(42.8,43,43.3,43.1,43,43.1,42.8,42.5,42.5,42.6,42.6,42.5)

df <- tibble(mois,A2022,A2021)
df <- df %>% 
  mutate(ecart = A2022-A2021)

MeanA2022 <- mean(df$A2022)

df %>% 
  pivot_longer(cols=c("A2022","A2021")) %>% 
  mutate(mois = factor(mois)) %>% 
  ggplot(aes(x = mois, y = ecart, fill = "ecart")) +
  geom_col(width = .4) +
  geom_line(aes(x = mois, y = value - MeanA2022, color = name, group = name), 
            size = 1, stat = "identity", inherit.aes = F) +
  scale_y_continuous("", breaks = seq(-1, 1, by = .5),
                     labels = MeanA2022+seq(-1, 1, by = .5),
                     sec.axis = sec_axis(~ ., name = "")) +
  scale_color_manual(breaks = c("A2021", "A2022"),
                     values = c("#00BFC4", "#F8766D")) +
  scale_fill_manual(breaks = c("ecart"),
                     values = c("#4472C4")) +
  labs(fill = NULL, color = NULL,
       x = NULL, y = NULL) +
  theme_bw() +
  theme(legend.position = "bottom",
        panel.grid.major.x = element_blank())
1 Like

Yes !!!!!! exactly !

Is it possible to set the value of the bars like this:

I can't set up geom_text correctly to do this

df %>% 
  pivot_longer(cols=c("A2022","A2021")) %>% 
  mutate(mois = factor(mois)) %>% 
  ggplot(aes(x = mois, y = ecart, fill = "ecart")) +
  geom_col(width = .4) +
  geom_line(aes(x = mois, y = value - MeanA2022, color = name, group = name), 
            size = 1, stat = "identity", inherit.aes = F) +
  scale_y_continuous("", breaks = seq(-1, 1, by = .5),
                     labels = MeanA2022+seq(-1, 1, by = .5),
                     sec.axis = sec_axis(~ ., name = "")) +
  scale_color_manual(breaks = c("A2021", "A2022"),
                     values = c("#00BFC4", "#F8766D")) +
  scale_fill_manual(breaks = c("ecart"),
                    values = c("#4472C4")) +
  labs(fill = NULL, color = NULL,
       x = NULL, y = NULL) +
  geom_text(aes(label = round(ecart,1)),
            vjust = 0.5
            , size = 2)+
  theme_bw() +
  theme(legend.position = "bottom",
        panel.grid.major.x = element_blank())

I made a few changes because I saw some problems, now should be ok.

Sure


library(tidyverse)

mois <- c(1:12)
A2022 <- c(43,43.2,43.5,43,42.8,42.7,42.2,42,42.6,42.7,42.9,43)
A2021 <- c(42.8,43,43.3,43.1,43,43.1,42.8,42.5,42.5,42.6,42.6,42.5)

df <- tibble(mois,A2022,A2021)
df <- df %>% 
  mutate(ecart = A2022-A2021)

MeanA2022 <- mean(df$A2022)


dflong <- df %>%
  pivot_longer(cols=c("A2022","A2021")) %>% 
  mutate(mois = factor(mois))

ytext <- df %>% 
  mutate(ecart2 = ifelse(ecart<0, ecart-0.05, ecart+0.05))

df %>%
  mutate(mois = factor(mois)) %>% 
  ggplot(aes(x = mois, y = ecart, fill = "ecart")) +
  geom_col(width = .4) +
  geom_line(data = dflong, aes(x = mois, y = value - MeanA2022, color = name, group = name), 
            size = 1, inherit.aes = F) +
  scale_y_continuous("", breaks = seq(-1, 1, by = .5),
                     labels = MeanA2022+seq(-1, 1, by = .5),
                     sec.axis = sec_axis(~ ., name = ""),
                     limits = c(-1,1)) +
  scale_color_manual(breaks = c("A2021", "A2022"),
                     values = c("#00BFC4", "#F8766D")) +
  scale_fill_manual(breaks = c("ecart"),
                    values = c("#4472C4")) +
  geom_text(data = ytext, aes(y = ecart2, label = round(ecart,1))) +
  labs(fill = NULL, color = NULL,
       x = NULL, y = NULL) +
  theme_bw() +
  theme(legend.position = "bottom",
        panel.grid.major.x = element_blank())


Good!!!

I have one last difficulty : I will make this code a function, so the values and their magnitude will be variable.
I have a problem with the position of the text on the columns.

For example with these 2 df :

#goat
annee <-  c(2021, 2021, 2021, 2021, 2021, 2021,2021, 2021, 2021, 2021, 2021, 2021, 2022, 2022, 2022,2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022)
mois <-  c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4,5, 6, 7, 8, 9, 10, 11)
espece <-  c(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2)
moy <-  c(45.57, 43.92, 42.44, 39.55, 37.52, 34.81, 34.65, 35.2, 36.76, 41.64, 45.14, 46.34, 44.68, 44.22, 42.16, 39.64,36.15, 34.59, 34.13, 33.71, 36.5, 39.55, 42.53)

df <- tibble(annee,mois,espece,moy)

#cow
annee <-  c(2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022) 
mois <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), 
espece<- c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,1, 1) 
moy <- c(43.89, 43.15, 42.88, 42.18, 42.04, 41.2, 41.1, 41.73, 42.28, 43.43, 44.32, 44.33, 43.23, 42.65, 42.28, 41.79, 40.64, 40.4, 40.42, 40.87, 42.24, 43.32, 43.6)
df <- tibble(annee,mois,espece,moy)

this is R code with few changes

N <- 2022

mg <- df %>% 
  mutate(annee=case_when(annee==N ~ "N",
                   annee==N-1 ~ "N_1")) %>% 
  pivot_wider(names_from = annee, values_from=moy) %>% 
  mutate(ecart=N-N_1) %>% 
  mutate(mois=as.factor(mois)) %>% 
  select(-espece) 


mg_long <- mg %>%  
  pivot_longer(cols=c("N_1","N"), names_to="annee",values_to="moy")
  
mean_mg <- round(mean(mg_long$moy, na.rm = T),0)
dif_mg <- round(max(mg_long$moy, na.rm = T)-min(mg_long$moy, na.rm = T),0)

ytext <- mg %>% 
  mutate(ecart2 = ifelse(ecart<0, ecart-0.05, ecart+0.05))


mg %>% 
  ggplot(aes(x = mois, y = ecart, fill = "ecart")) +
  geom_col(width = .4) +
  geom_line(data=mg_long,aes(x=mois, y=moy- mean_mg, color = annee, group = annee), 
            size = 2, stat = "identity", inherit.aes = F) +
  scale_y_continuous("", breaks = seq(-dif_mg/2-1, dif_mg/2+1, by = .5),
                     labels = round(mean_mg+seq(-dif_mg/2-1, dif_mg/2+1, by = .5),2),
                     sec.axis = sec_axis(~ ., name = ""))+
  scale_color_manual(breaks = c("N_1", "N"),
                     values = c("#00BFC4", "#F8766D")) +
  scale_fill_manual(breaks = c("ecart"),
                    values = c("#4472C4")) +
  labs(fill = NULL, color = NULL,
       x = NULL, y = NULL) +
  geom_text(data = ytext, aes(y = ecart2, label = round(ecart,2)), size=3) +
  theme_bw() +
  theme(legend.position = "bottom",
        panel.grid.major.x = element_blank())

And here are the 2 graphs :
cow :

goat :

Probably the easiest way is to change it manually in the function, for example here you can play with "1" value

myplot <- function(mydf, mydist, mytitle){

distance = abs(max(mg$ecart, na.rm = T))*mydist
  
ytext <- mg %>% 
    mutate(ecart2 = ifelse(ecart<0, ecart-distance, ecart+distance))
    
mydf %>% 
  ggplot(aes(x = mois, y = ecart, fill = "ecart")) +
  geom_col(width = .4) +
  geom_line(data=mg_long,aes(x=mois, y=moy- mean_mg, color = annee, group = annee), 
            size = 2, stat = "identity", inherit.aes = F) +
  scale_y_continuous("", breaks = seq(-dif_mg/2-1, dif_mg/2+1, by = .5),
                     labels = round(mean_mg+seq(-dif_mg/2-1, dif_mg/2+1, by = .5),2),
                     sec.axis = sec_axis(~ ., name = ""))+
  scale_color_manual(breaks = c("N_1", "N"),
                     values = c("#00BFC4", "#F8766D")) +
  scale_fill_manual(breaks = c("ecart"),
                    values = c("#4472C4")) +
  labs(fill = NULL, color = NULL,
       x = NULL, y = NULL,
       title = mytitle) +
  geom_text(data = ytext, aes(y = ecart2, label = round(ecart,2)), size=3) +
  theme_bw() +
  theme(legend.position = "bottom",
        panel.grid.major.x = element_blank())
}

myplot(df, 1, "Goat")


Thanks, but in the end I chose another solution with cowplot package .

But the grid is not aligned, because the labels on the Y axis are not the same size.

I have not found how to define a dimension for the grid area.

1 Like

try patchwork.
example

library(patchwork)

plot1 <- ggplot(......)
plot2 <- ggplot(......)

plot1 / plot2

thanks, I will have a look !

I'm trying patchwork, but the problem is the same :frowning:

this is R code :

dataframe : df_cl_mensuel_total

df_cl_mensuel_total  <- tibble::tribble(
   ~annee, ~mois, ~total_mensuel,
      "N",     1,         114336,
      "N",     2,         100921,
      "N",     3,         155239,
      "N",     4,         172962,
      "N",     5,         145060,
      "N",     6,         174577,
      "N",     7,         142145,
      "N",     8,          43597,
      "N",     9,         132987,
      "N",    10,         146926,
      "N",    11,         133297,
    "N_1",     1,         122805,
    "N_1",     2,         104340,
    "N_1",     3,         158145,
    "N_1",     4,         177546,
    "N_1",     5,         161997,
    "N_1",     6,         170355,
    "N_1",     7,         146303,
    "N_1",     8,          51929,
    "N_1",     9,         127206,
    "N_1",    10,         153255,
    "N_1",    11,         132363,
    "N_1",    12,         123454,
    "N_2",     1,         124021,
    "N_2",     2,         121080,
    "N_2",     3,          88682,
    "N_2",     4,          36015,
    "N_2",     5,         170379,
    "N_2",     6,         171704,
    "N_2",     7,         169914,
    "N_2",     8,          62172,
    "N_2",     9,         134866,
    "N_2",    10,         169902,
    "N_2",    11,         137301,
    "N_2",    12,         131686
   )

df_cl_total_mensuel <-  df_cl%>% 
    mutate(
    annee=case_when(annee==N ~ "N",
                   annee==N-1 ~ "N_1",
                   annee==N-2 ~"N_2")) %>% 
  select(annee,mois,nb) %>% 
  group_by(annee,mois) %>% 
    summarise(total_mensuel=sum(nb))
  
gg_cl_total_mensuel <- df_cl_total_mensuel %>% 
  ggplot() + 
  aes(x=mois,y=total_mensuel, color=annee, group=annee) + 
  geom_line(size=1)+
  geom_point(shape = 1,size = 2)+
    geom_text(data = df_cl_total_mensuel %>% filter(annee=="N"),
            aes(y=total_mensuel,label=format(total_mensuel,big.mark=" ", scientific = F) ), 
            position = "dodge",size = 3,alpha = 0.9,segment.size = .2,segment.alpha = .5,
                    force = 1,box.padding = 1,color="black")+
  scale_color_manual(labels=c(N,N_1,N_2),
                     values = c(N="#F8766D",N_1="#00BFC4",N_2="#D6D6D6"))+
  scale_y_continuous(labels = function (x) format(x,big.mark=" ",scientic = F ))+
  theme_minimal()+
  labs(title = "Nombre",
       color="Année",
       y="Nombre de flacons",
       x="mois")+ 
  theme(legend.position= "right",
        legend.text = element_text(size=6),
        legend.title = element_text(size=6),
        plot.title = element_text(hjust = 0.5),
        axis.text.x = element_blank(),
        axis.text.y = element_text(size=8),
        axis.title.y = element_text(size=10),
        title = element_text(size=10)
  )


df_cl_ecart_mensuel <- df_cl_total_mensuel %>% 
  filter(annee %in% c("N","N_1")) %>% 
  pivot_wider(names_from = annee,values_from = total_mensuel) %>% 
  mutate(ecart = N-N_1,
         mois=as.factor(mois))
  
lim_ecart_max <- max(df_cl_ecart_mensuel$ecart,na.rm=T)*1.2
lim_ecart_min <- min(df_cl_ecart_mensuel$ecart, na.rm=T)*1.2

gg_cl_ecart_mensuel <- df_cl_ecart_mensuel %>% 
  ggplot()+
   aes(x=mois,y=ecart, fill="ecart", label=ecart)+
  geom_col(width = 0.5, alpha=0.5)+
  scale_fill_manual(breaks = c("ecart"),values = c("blue"),labels= c("écart")) +
  geom_text(aes(x=mois,y=ecart,label=format(ecart,big.mark=" ", scientific = F), 
                vjust = 0.5 - sign(ecart)/2, hjust=0.5),size=2,color="black")+
  scale_y_continuous(labels = function (x) format(x,big.mark=" ", scientific = F))+
  labs(y="Nb flacons")+
  geom_hline(yintercept=0)+
  coord_cartesian(xlim =c(1, 12), 
                  ylim = c(lim_ecart_min, lim_ecart_max))+
  theme_minimal()+
  theme(axis.text.y = element_text(size=8, face = "italic"),
        axis.title.y = element_text(size=8,face = "italic"),
        axis.text.x = element_text(size=8),
        axis.title.x = element_text(size = 8, hjust = 1),
        legend.text = element_text(size=7),
         legend.title = element_text(color="transparent")
        )

gg_cl_total_mensuel/gg_cl_ecart_mensuel

if I use cowplot, the problem is the same

   plot_grid(gg_cl_total_mensuel,gg_cl_ecart_mensuel,ncol=1,rel_heights = c(2,1))+
   theme(plot.background = element_rect(color = "grey"))

thanks

Here we are:

The problem is this:

#first plot 
class(df_cl_total_mensuel$mois)

[1] "numeric"

#second plot 
class(df_cl_ecart_mensuel$mois)

[1] "factor"

both must be factors, so just convert it:

gg_cl_total_mensuel <- df_cl_total_mensuel %>% 
  mutate(mois = factor(mois)) %>% 
  ggplot() + .........

so nice !!!

thanks a lot !

is it possible to draw a line around the final graph ?

When I run this code, only the second graph has a line around

gg <- g1+g2+plot_layout(ncol=1, heights = c(2,1))
  
gg <- gg + theme(plot.background = element_rect(color = "grey"))

I’m not sure about it, I’ll search for it

gg + plot_annotation(theme = theme(
                     plot.background = element_rect(
                                       color = "grey",
                                       linewidth = 100)))
1 Like

It works very well!

I still have a problem with the labels on the bar chart.
On the graph alone, the position is perfect, and when I combine the graphs, the labels are sometimes truncated

You can try using limits for example

…+ 
 scale_y_continuous(limits = c(-30, 30))

Or try adding

+ coord_cartesian(clip="off") 

I try adding coord_cartesian(clip="off")

the result is sometimes unexpected :frowning: