0

I am trying to implement the diagram 1 from Excel to Shiny. So far I got this code with the resulting diagram 2.

ggplot(filteredData(), aes(x=interaction(month, year), y=sum)) 
+ geom_bar(stat="identity")  + facet_grid(. ~ X)  + theme(legend.position="none")

I want to group month and year like in the Excel example, so hat you have only the month counter ("1", "2", ...) in the first row of the legend and the year ("2016", "2017", ...) in the second. The number of months can vary.

The data set looks like:

X    year  month  sum
10   2016  1      450
10   2016  2      670
...  ...   ...    ...
10   2017  1      200
11   2016  1      460
  • Can you please add an example of the data set? Have a look a this link on [how to make a good reproducible question](http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example). – thepule Aug 31 '16 at 10:00

2 Answers2

2

I slightly changed the data set, this is the closest I got to your specs:

df <- read.table(text = "X    year  month  sum
10   2016  1      450
10   2016  2      670
10   2017  1      200
11   2016  1      460
11   2017  2      500", header = T)

# Notice the variable type for month and year
df$month <- as.factor(df$month)
df$year <- as.factor(df$year)
df$X <- as.factor(df$X)

 ggplot(df, aes(x = month, y = sum)) + geom_bar(stat = "identity") + 
  facet_grid(.~X + year,
             switch = "x", # Moves the labels from the top to the bottom
             labeller = label_both # Adds the labels to the year and X variables
             ) + 
  xlab("") # Removes the month label

Result: enter image description here

Or if you want to drop unused levels:

ggplot(df, aes(x = month, y = sum)) + geom_bar(stat = "identity") + 
  facet_grid(.~X + year,
             switch = "x", # Moves the labels from the top to the bottom
             labeller = label_both, # Adds the labels to the year and X variables
             scales = "free_x") + 
  xlab("") # Removes the month legend

enter image description here

thepule
  • 1,721
  • 1
  • 12
  • 22
  • Hi @thepule, I used your method to solve my issue. Now I have the problem that I want all bars to have the same width. At the moment, this is not the case, because in 2016, there are only the months 9 - 12 left, in 2017 there are the months from 1 - 9, but both years have the same space for all bars. So the worst case is december, when one bar fills the whole 2016 space and eleven bars have to share the 2017 space. –  Sep 19 '16 at 08:03
  • My code: `ggplot(data01, aes_string(x = "month", y = "sum", fill = "cat") + geom_bar(stat = "identity") + facet_grid(.~ year, scales = "free_x") + xlab("") + ylab("") + theme_light() + theme(axis.text.x = element_text(angle = 90))` Would be so cool if you had an advice for me! :) –  Sep 19 '16 at 08:04
  • I am not sure how to approach this. I will think about it. – thepule Sep 19 '16 at 08:21
  • Ok, thanks a lot! Maybe there is the possibility to make the width of the whole plot depending on the number of bars? –  Sep 19 '16 at 08:34
2

You can get a little more complex and use cowplot to merge the plots together. You could automate this using lapply to loop through your unique values, though that is probably overkill for just two groups.

library(ggplot2)
library(cowplot)
library(dplyr)

# Return to default theme, as cowplot sets its own
theme_set(theme_gray())

# Save y limits to get same scale
myYlims <- c(0, ceiling(max(df$sum)/100)*100)


# Generate each plot
x10 <-
  ggplot(df %>%
           filter(X == 10)
         , aes(x = month, y = sum)) + geom_bar(stat = "identity") + 
  facet_grid(~ year,
             switch = "x") +
  panel_border() +
  coord_cartesian(ylim = myYlims) +
  xlab("X = 10")


x11 <-
  ggplot(df %>%
           filter(X == 11)
         , aes(x = month, y = sum)) + geom_bar(stat = "identity") + 
  facet_grid(~ year,
             switch = "x") +
  panel_border() +
  coord_cartesian(ylim = myYlims) +
  xlab("X = 11")


# Put the plots together
plot_grid(x10
          , x11 +
            theme(axis.title.y = element_blank()
                  , axis.text.y = element_blank()
                  , axis.ticks.y = element_blank())
          , rel_widths = c(1.1,1)
          )

enter image description here

Here is an approach to automate this, including more complex data to justify the automation. Note that you will need to play with the aspect ratio of your output and with the rel_widths option to make it look decent:

df <-
  data.frame(
    X = rep(1:6, each = 9)
    , year = rep(rep(2016:2018, each = 3),3)
    , month = rep(1:3, 6)
    , sum = rnorm(9*6, 700, 100)
  )


# Notice the variable type for month and year
df$month <- as.factor(df$month)
df$year <- as.factor(df$year)
df$X <- as.factor(df$X)


# Save y limits to get same scale
myYlims <- c(0, ceiling(max(df$sum)/100)*100)


# Generate each plot
eachPlot <- lapply(levels(df$X), function(thisX){
  ggplot(df %>%
           filter(X == thisX)
         , aes(x = month, y = sum)) +
    geom_bar(stat = "identity") + 
    facet_grid(~ year,
               switch = "x") +
    panel_border() +
    coord_cartesian(ylim = myYlims) +
    xlab(paste("X =", thisX))
})


# Remove axes from all but the first
eachPlot[-1] <- lapply(eachPlot[-1], function(x){
  x +
    theme(axis.title.y = element_blank()
          , axis.text.y = element_blank()
          , axis.ticks.y = element_blank()
    )
})

# Put the plots together
plot_grid(plotlist = eachPlot
          , rel_widths = c(1.4, rep(1, length(eachPlot)-1))
          , nrow = 1
)

enter image description here

Mark Peterson
  • 9,370
  • 2
  • 25
  • 48
  • Hi Mark and thank you for your answer. My report has a huge amount of X's. Could you show me how you generate this with various numbers of X? –  Aug 31 '16 at 14:46
  • @F.Gr. I added some more complex data and showed an example of the `lapply` approach that I would take. – Mark Peterson Aug 31 '16 at 15:06
  • How does this change if X is not a number but a string? Tried to adjust it but I am kind of new to R and so it didn't work :( –  Aug 31 '16 at 15:28
  • In the sample data, `X` is a factor. What the label looks like (number vs. string) should not matter. You may need to convert it to a factor (using either `factor` or `as.factor`). What happened when it "didn't work"? – Mark Peterson Aug 31 '16 at 15:49