Multirow axis labels with nested grouping variables

前端 未结 6 1962
后悔当初
后悔当初 2020-11-22 16:42

I would like the levels of two different nested grouping variables to appear on separate lines below the plot, and not in the legend. What I have right now is this code:

相关标签:
6条回答
  • 2020-11-22 16:58

    The strip.position argument in facet_wrap() and switch argument in facet_grid() since ggplot2 2.2.0 now makes the creation of a simple version of this plot fairly straightforward via faceting. To give the plot the uninterrupted look, set the panel.spacing to 0.

    Here's the example using the dataset with a different number of Groups per Category from @agtudy's answer.

    • I used scales = "free_x" to drop the extra Group from the Categories that don't have it, although this won't always be desirable.
    • The strip.position = "bottom" argument moves the facet labels to the bottom. I removed the strip background all together with strip.background, but I could see that leaving the strip rectangle would be useful in some situations.
    • I used width = 1 to make the bars within each Category touch - they'd have spaces between them by default.

    I also use strip.placement and strip.background in theme to get the strips on the bottom and remove the strip rectangle.

    The code for versions of ggplot2_2.2.0 or newer:

    ggplot(data = data, aes(x = Group, y = Value, fill = Group)) + 
        geom_bar(stat = "identity", width = 1) +
        geom_text(aes(label = paste(Value, "%")), vjust = -0.25) +
        facet_wrap(~Category, strip.position = "bottom", scales = "free_x") +
        theme(panel.spacing = unit(0, "lines"), 
             strip.background = element_blank(),
             strip.placement = "outside")
    

    You could use space= "free_x" in facet_grid() if you wanted all the bars to be the same width regardless of how many Groups per Category. Note that this uses switch = "x" instead of strip.position. You also might want to change the label of the x axis; I wasn't sure what it should be, maybe Category instead of Group?

    ggplot(data = data, aes(x = Group, y = Value, fill = Group)) + 
        geom_bar(stat = "identity", width = 1) +
        geom_text(aes(label = paste(Value, "%")), vjust = -0.25) +
        facet_grid(~Category, switch = "x", scales = "free_x", space = "free_x") +
        theme(panel.spacing = unit(0, "lines"), 
             strip.background = element_blank(),
             strip.placement = "outside") + 
        xlab("Category")
    

    Older code versions

    The code for ggplot2_2.0.0, when this feature was first introduced, was a little different. I've saved it below for posterity:

    ggplot(data = data, aes(x = Group, y = Value, fill = Group)) + 
        geom_bar(stat = "identity") +
        geom_text(aes(label = paste(Value, "%")), vjust = -0.25) +
        facet_wrap(~Category, switch = "x", scales = "free_x") +
        theme(panel.margin = unit(0, "lines"), 
             strip.background = element_blank())
    
    0 讨论(0)
  • 2020-11-22 16:59

    You can create a custom element function for axis.text.x.

    enter image description here

    library(ggplot2)
    library(grid)
    
    ## create some data with asymmetric fill aes to generalize solution 
    data <- read.table(text = "Group Category Value
                       S1 A   73
                       S2 A   57
                       S3 A   57
                       S4 A   57
                       S1 B   7
                       S2 B   23
                       S3 B   57
                       S1 C   51
                       S2 C   57
                       S3 C   87", header=TRUE)
    
    # user-level interface 
    axis.groups = function(groups) {
      structure(
        list(groups=groups),
        ## inheritance since it should be a element_text
        class = c("element_custom","element_blank")  
      )
    }
    # returns a gTree with two children: 
    # the categories axis
    # the groups axis
    element_grob.element_custom <- function(element, x,...)  {
      cat <- list(...)[[1]]
      groups <- element$group
      ll <- by(data$Group,data$Category,I)
      tt <- as.numeric(x)
      grbs <- Map(function(z,t){
        labs <- ll[[z]]
        vp = viewport(
                 x = unit(t,'native'), 
                 height=unit(2,'line'),
                 width=unit(diff(tt)[1],'native'),
                 xscale=c(0,length(labs)))
        grid.rect(vp=vp)
        textGrob(labs,x= unit(seq_along(labs)-0.5,
                                    'native'),
                 y=unit(2,'line'),
                 vp=vp)
      },cat,tt)
      g.X <- textGrob(cat, x=x)
      gTree(children=gList(do.call(gList,grbs),g.X), cl = "custom_axis")
    }
    
    ## # gTrees don't know their size 
    grobHeight.custom_axis = 
      heightDetails.custom_axis = function(x, ...)
      unit(3, "lines")
    
    ## the final plot call
    ggplot(data=data, aes(x=Category, y=Value, fill=Group)) + 
      geom_bar(position = position_dodge(width=0.9),stat='identity') +
      geom_text(aes(label=paste(Value, "%")),
                position=position_dodge(width=0.9), vjust=-0.25)+
      theme(axis.text.x = axis.groups(unique(data$Group)),
            legend.position="none")
    
    0 讨论(0)
  • 2020-11-22 17:05

    A very simple solution which gives a similar (though not identical) result is to use faceting. The downside is that the Category label is above rather than below.

    ggplot(data=data, aes(x=Group, y=Value, fill=Group)) +
      geom_bar(position = 'dodge', stat="identity") +
      geom_text(aes(label=paste(Value, "%")), position=position_dodge(width=0.9), vjust=-0.25) + 
      facet_grid(. ~ Category) + 
      theme(legend.position="none")
    

    Using faceting to provide secondary label

    0 讨论(0)
  • 2020-11-22 17:05

    @agstudy already answered this question and I'm going to use it myself, but if you'd accept something uglier, but simpler, this is what I came with before his answer:

    data <- read.table(text = "Group Category Value
        S1 A   73
        S2 A   57
        S1 B   7
        S2 B   23
        S1 C   51
        S2 C   87", header=TRUE)
    
    p <- ggplot(data=data, aes(x=Category, y=Value, fill=Group))
    p + geom_bar(position = 'dodge') +
      geom_text(aes(label=paste(Value, "%")), position=position_dodge(width=0.9),   vjust=-0.25) +
      geom_text(colour="darkgray", aes(y=-3, label=Group),  position=position_dodge(width=0.9), col=gray) +
      theme(legend.position = "none", 
        panel.background=element_blank(),
        axis.line = element_line(colour = "black"),
        axis.line.x = element_line(colour = "white"),
        axis.ticks.x = element_blank(),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        panel.background = element_blank()) +
      annotate("segment", x = 0, xend = Inf, y = 0, yend = 0)
    

    Which will give us:

    enter image description here

    0 讨论(0)
  • 2020-11-22 17:12

    Here's another solution using a package I'm working on for grouped bar charts (ggNestedBarChart):

    data <- read.table(text = "Group Category Value
                       S1 A   73
                       S2 A   57
                       S3 A   57
                       S4 A   57
                       S1 B   7
                       S2 B   23
                       S3 B   57
                       S1 C   51
                       S2 C   57
                       S3 C   87", header = TRUE)
    
    devtools::install_github("davedgd/ggNestedBarChart")
    library(ggNestedBarChart)
    library(scales)
    
    p1 <- ggplot(data, aes(x = Category, y = Value/100, fill = Category), stat = "identity") +
      geom_bar(stat = "identity") +
      facet_wrap(vars(Category, Group), strip.position = "top", scales = "free_x", nrow = 1) +
      theme_bw(base_size = 13) +
      theme(panel.spacing = unit(0, "lines"),
            strip.background = element_rect(color = "black", size = 0, fill = "grey92"),
            strip.placement = "outside",
            axis.text.x = element_blank(),
            axis.ticks.x = element_blank(),
            panel.grid.major.y = element_line(colour = "grey"),
            panel.grid.major.x = element_blank(),
            panel.grid.minor = element_blank(),
            panel.border = element_rect(color = "black", fill = NA, size = 0),
            panel.background = element_rect(fill = "white"),
            legend.position = "none") + 
      scale_y_continuous(expand = expand_scale(mult = c(0, .1)), labels = percent) + 
      geom_text(aes(label = paste0(Value, "%")), position = position_stack(0.5), color = "white", fontface = "bold")
    
    ggNestedBarChart(p1)
    
    ggsave("p1.png", width = 10, height = 5)
    

    Note that ggNestedBarChart can group as many levels as necessary and isn't limited to just two (i.e., Category and Group in this example). For instance, using data(mtcars):

    Code for this example is on the GitHub page.

    0 讨论(0)
  • 2020-11-22 17:17

    An alternative to agstudy's method is to edit the gtable and insert an "axis" calculated by ggplot2,

    p <- ggplot(data=data, aes(x=Category, y=Value, fill=Group)) + 
      geom_bar(position = position_dodge(width=0.9),stat='identity') +
      geom_text(aes(label=paste(Value, "%")),
                position=position_dodge(width=0.9), vjust=-0.25)
    
    axis <- ggplot(data=data, aes(x=Category, y=Value, colour=Group)) +
      geom_text(aes(label=Group, y=0),
                position=position_dodge(width=0.9))
    
    annotation <- gtable_filter(ggplotGrob(axis), "panel", trim=TRUE)
    annotation[["grobs"]][[1]][["children"]][c(1,3)] <- NULL #only keep textGrob
    
    library(gtable)
    g <- ggplotGrob(p)
    gtable_add_grobs <- gtable_add_grob # let's use this alias
    g <- gtable_add_rows(g, unit(1,"line"), pos=4)
    g <- gtable_add_grobs(g, annotation, t=5, b=5, l=4, r=4)
    grid.newpage()
    grid.draw(g)
    

    enter image description here

    0 讨论(0)
提交回复
热议问题