1

I have a ggplot with 3 variables. I want to add a 4th variable "circumstances_bite_avoidability" which would be denoted by the coloured border of the bars. How do I do this? If it can be achieved, I don't have to make a separate graph just to show that remaining variable.

My ggplot code:

library(tidyverse)
library (ggpattern)

sur %>% 
  filter(!(species_broad %in% c("Monkey", "Cattle"))) %>%
  drop_na(circumstances_bite_broad_intended) %>%
  add_count(species_broad) %>%
  mutate(y_species_broad = 
           reorder(
             factor(paste0(species_broad, "\n", "(n = ", n, ")")),
             as.numeric(species_broad)
           )) %>% 
  ggplot(aes(y= y_species_broad, fill=circumstances_bite_broad, pattern = circumstances_bite_broad_intended)) + 
  geom_bar_pattern(
    colour = "black", 
    position = "fill",
    pattern_density = 0.1,
    pattern_spacing = 0.015,
    pattern_key_scale_factor = 0.9
  ) +
  scale_x_continuous(labels=c("0","25%","50%","75%","100%")) +
  labs(x="Proportion of responses", y="Animal species", fill="Circumstances", pattern="Intentional Human Engagement") +
  ggtitle("Circumstances of bite in different animal species") + 
  scale_fill_manual(values =c("Intentional Human Engagement" = "#EC7979",
                              "Unintentional Human Engagement" = "lightblue",
                              "Apparently unprovoked" = "darkblue")) + 
  scale_pattern_manual(values=c("Threatening Engagement" = "stripe",
                                "Friendly Engagement" = "circle",
                                "Practical Engagement" = "crosshatch",
                                "Unintentional Human Engagement" = "none",
                                "Apparently unprovoked" = "none"),
                       breaks = c("Threatening Engagement", "Friendly Engagement", "Practical Engagement") )+
  theme(legend.key = element_rect(fill = "white")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white")),
         fill = guide_legend(override.aes = list(pattern = "none")))

My desired picture: enter image description here

My data:

structure(list(species_broad = structure(c(1L, 1L, 1L, 2L, 1L, 
1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 3L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 3L, 5L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 3L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 2L, 2L, 2L, 3L, 1L, 2L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 3L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 
2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 
1L, 3L, 1L, 1L, 1L, 2L, 2L, 3L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 3L, 3L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 2L, 1L, 3L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 3L, 2L, 2L, 3L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 4L, 1L, 1L, 1L, 3L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 3L, 
1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 4L, 1L, 1L, 
1L, 2L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 3L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 3L, 1L), levels = c("Dog", 
"Cat", "Rodent", "Monkey", "Cattle"), class = "factor"), circumstances_bite_broad_intended = structure(c(2L, 
4L, 1L, 3L, 4L, 4L, 4L, 5L, 5L, 3L, 5L, 1L, 4L, 5L, 5L, 1L, 5L, 
5L, 2L, 5L, 4L, 3L, 5L, 2L, 2L, 5L, 4L, 3L, 3L, 5L, 1L, 5L, 1L, 
1L, 5L, 5L, 4L, 4L, 5L, 1L, 5L, 5L, 1L, 1L, 5L, 5L, 2L, 3L, 5L, 
NA, 4L, 5L, 4L, 4L, 4L, 1L, 4L, 1L, 5L, 4L, 5L, 2L, 5L, 1L, 3L, 
1L, 2L, 4L, 5L, 5L, 1L, 5L, 5L, 5L, 5L, 5L, 5L, 2L, NA, 1L, 2L, 
3L, 5L, 1L, 2L, 3L, 4L, 4L, 4L, 4L, 4L, 3L, 4L, 1L, 4L, 4L, 3L, 
4L, 3L, 4L, 3L, 4L, 5L, 4L, 2L, 4L, 3L, 4L, 2L, 4L, 3L, 5L, 1L, 
5L, 5L, 3L, 2L, 2L, 5L, 4L, 4L, 4L, 4L, 4L, 3L, 5L, 3L, 4L, 4L, 
4L, 1L, 1L, 2L, 4L, 5L, 4L, 4L, 4L, 3L, 4L, 5L, 5L, 4L, 4L, 5L, 
5L, 5L, 5L, 4L, 4L, 5L, 4L, 2L, 5L, 5L, 5L, 5L, 3L, 5L, 1L, 5L, 
4L, 3L, 5L, 1L, 2L, 2L, 4L, 4L, 4L, 5L, 4L, 5L, 5L, 4L, 2L, 4L, 
2L, 4L, 4L, 4L, 4L, 2L, 3L, 2L, 2L, 4L, 4L, 1L, 5L, 4L, 4L, 4L, 
4L, 4L, 4L, 1L, 4L, 2L, 4L, 3L, 5L, 5L, 2L, 4L, 1L, 4L, 2L, 4L, 
4L, 4L, 4L, 4L, 1L, 1L, 2L, 1L, 4L, 5L, 4L, 4L, 4L, 4L, 4L, 2L, 
4L, 4L, 3L, 5L, 4L, 4L, 4L, 4L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 
4L, 2L, 3L, 1L, 1L, 4L, 4L, 4L, 4L, 1L, 4L, 4L, 4L, 4L, 5L, 4L, 
2L, 4L, 4L, 4L, 4L, 2L, 4L, 4L, 2L, 5L, 5L, 5L, 5L, 4L, 4L, 4L, 
5L, 5L, 5L, 4L, 1L, 4L, 4L, 5L, 1L, 5L, 2L, 1L, 2L, 1L, 4L, 4L, 
4L, 5L, 2L, 4L, 2L, 4L, 4L, 4L, 2L, 5L, 4L, 4L, 4L, 1L, 4L, 3L, 
4L, 4L, 2L, 5L, 2L, 2L, 3L, 5L, 2L, 4L, 5L, 1L, 4L, 4L, 4L, 2L, 
4L, 5L, 3L, 2L, 4L, 3L, 4L, 4L, 4L, 5L, 4L, 1L, 4L, 4L, 5L, 4L, 
4L, 5L, 4L, 5L, 4L, 4L, 4L, 5L, 2L, 1L, 3L, 5L, 4L, 5L, 5L, 5L, 
1L, 5L), levels = c("Threatening Engagement", "Friendly Engagement", 
"Practical Engagement", "Unintentional Human Engagement", "Apparently unprovoked"
), class = "factor"), circumstances_bite_broad = structure(c(1L, 
2L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 1L, 3L, 1L, 2L, 3L, 3L, 1L, 3L, 
3L, 1L, 3L, 2L, 1L, 3L, 1L, 1L, 3L, 2L, 1L, 1L, 3L, 1L, 3L, 1L, 
1L, 3L, 3L, 2L, 2L, 3L, 1L, 3L, 3L, 1L, 1L, 3L, 3L, 1L, 1L, 3L, 
NA, 2L, 3L, 2L, 2L, 2L, 1L, 2L, 1L, 3L, 2L, 3L, 1L, 3L, 1L, 1L, 
1L, 1L, 2L, 3L, 3L, 1L, 3L, 3L, 3L, 3L, 3L, 3L, 1L, NA, 1L, 1L, 
1L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 
2L, 1L, 2L, 1L, 2L, 3L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 3L, 1L, 
3L, 3L, 1L, 1L, 1L, 3L, 2L, 2L, 2L, 2L, 2L, 1L, 3L, 1L, 2L, 2L, 
2L, 1L, 1L, 1L, 2L, 3L, 2L, 2L, 2L, 1L, 2L, 3L, 3L, 2L, 2L, 3L, 
3L, 3L, 3L, 2L, 2L, 3L, 2L, 1L, 3L, 3L, 3L, 3L, 1L, 3L, 1L, 3L, 
2L, 1L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 2L, 3L, 3L, 2L, 1L, 2L, 
1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 3L, 2L, 2L, 2L, 
2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 3L, 3L, 1L, 2L, 1L, 2L, 1L, 2L, 
2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 3L, 2L, 2L, 2L, 2L, 2L, 1L, 
2L, 2L, 1L, 3L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 3L, 2L, 
1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 3L, 3L, 3L, 3L, 2L, 2L, 2L, 
3L, 3L, 3L, 2L, 1L, 2L, 2L, 3L, 1L, 3L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 3L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 3L, 2L, 2L, 2L, 1L, 2L, 1L, 
2L, 2L, 1L, 3L, 1L, 1L, 1L, 3L, 1L, 2L, 3L, 1L, 2L, 2L, 2L, 1L, 
2L, 3L, 1L, 1L, 2L, 1L, 2L, 2L, 2L, 3L, 2L, 1L, 2L, 2L, 3L, 2L, 
2L, 3L, 2L, 3L, 2L, 2L, 2L, 3L, 1L, 1L, 1L, 3L, 2L, 3L, 3L, 3L, 
1L, 3L), levels = c("Intentional Human Engagement", "Unintentional Human Engagement", 
"Apparently unprovoked"), class = "factor"), circumstances_bite_avoidability = structure(c(1L, 
2L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 
2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 
1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 
NA, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 
1L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, NA, 1L, 1L, 
1L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 
2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 
2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 
2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 
2L, 1L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 
1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 
2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 
2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 
1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 
2L, 2L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 
2L, 2L, 1L, 1L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 
1L, 2L), levels = c("Avoidable", "Unavoidable"), class = "factor")), row.names = c(NA, 
-355L), class = "data.frame")
  • 1
    What you are asking is possible, but your plot already requires quite a bit of cognitive effort on the part of your audience. Would you be open to different ways of displaying the same data? – Allan Cameron Aug 14 '23 at 11:52
  • There is a lot of information in this plot. Would't it be easier to have two separate plots - One for avoidable vs One for unavoidable (e.g. with `facet_wrap()`) – Yacine Hajji Aug 14 '23 at 11:54
  • [_Again_](https://stackoverflow.com/questions/76889621/adding-symbols-to-a-ggplot-graph-as-a-3rd-variable#comment135548885_76889621), your CODE references `species_broad` and your SAMPLE DATA does not. Please update your sample data for this and future questions. If it doesn't fail for you, that likely means that you have a vector in your calling environment named `species_broad`, in which case it had better be correct, otherwise you are corrupting your plots. – r2evans Aug 14 '23 at 12:01
  • Also, reproducible questions need to include non-base packages. I'm inferring `ggpattern`, please be explicit. When you edit your question to fix the column name problem, please add the package(s). – r2evans Aug 14 '23 at 12:03
  • 1
    @r2evans Sorry for that. I have now updated the sample data now with species_broad. – Rakesh Chand Aug 14 '23 at 12:06
  • @AllanCameron I have plots where the data are represented separately. The issue is that it makes multiple plots as I have other variables that are also being reported. The big chunk of the report will be then taken by presenting these individual graphs but this way I can show it in one figure, but explain in detail in the text. Once I know how to do this and visualise this graph, I will definitely consider whether to stick with this or represent the information into separate graphs. – Rakesh Chand Aug 14 '23 at 12:14
  • 1
    @AllanCameron I'm generally in agreement against adding extra dimensions to a plot, but I don't think it's an issue here: what is being done is a highlighting of grouping, not really adding a dimension with different boundaries and such. – r2evans Aug 14 '23 at 12:56
  • @r2evans I know what you mean, but then this doesn't add anything but clutter to the plot. The avoidable / unavoidable variable seems perfectly correlated with the 'intentional human engagement' level of `circumstances_bite_broad`, which is already coloured pink, while the other groups are blue. A caption saying 'Please note that intentional human contact is avoidable' does the same thing with less effort on the part of both the coder and the audience (if it needs said at all!). An extra legend and an extra layer of complexity seems like chart junk to me (though kudos for the technical expo!) – Allan Cameron Aug 14 '23 at 13:14
  • 1
    @AllanCameron it's "mostly" correlated, imperfectly but very closely. It's for that reason that I think its addition is a little noisy but does not add significant confusion to the plot. I respect your opinion (here and in general)! And I think this is really pushing the limits ... but I'm not opposed enough to not post an answer. – r2evans Aug 14 '23 at 13:25
  • 2
    @r2evans the feeling is mutual. I think you are right to post an answer here, since the question is about _how_ one could achieve this, and your answer may help others in the future. Data viz design is off-topic, but answering the question as asked with an informative exposition never is. – Allan Cameron Aug 14 '23 at 13:33
  • 1
    @r2evans Thank you so much for the plot. You are a genius with ggplot. You've helped me out so much today. God bless you. I will take into consideration your suggestion regarding adding too much information and clutter in the graph and see what would be the best way ahead. For now, just showing so much information in this graph is amazing. – Rakesh Chand Aug 14 '23 at 13:42
  • 1
    @AllanCameron If you're curious, see the edit to my answer below. I think your caution against adding extra dimensions is not the only risk, there are other issues related to how they are added in relation to other layers (geoms). – r2evans Aug 14 '23 at 14:35

1 Answers1

1

NOTE: I used sizes here that visually enhance their presence, but they also have an undesirable side-effect: obscuring the fill colors. Looking at the "Rodent/Unintentional" (light-blue) area, in the plots below it is not hard to imagine that a smaller proportion would not show any light-blue out from under the green boundary.

See for instance:

enter image description here

note how the light-blue is a bit smaller. In plots like this, our eyes tend to associate area (or perhaps linear-distance from left-to-right) with proportion, and the light-blue area is disadvantaged by the green boundary.

This can be mitigated by setting geom_bar(..., size=1), but then it may be incredibly difficult to see the actual boundary.

For this reason, I suggest that you don't try to wrap with rectangles (using geom_bar, as I suggest in the first attempt, saved below), instead I suggest line segments above (or below) the bar plots. I'll still use geom_bar, though, and make the width=0 to turn them into segments.

sur %>% 
  filter(!(species_broad %in% c("Monkey", "Cattle"))) %>%
  drop_na(circumstances_bite_broad_intended) %>%
  add_count(species_broad) %>%
  mutate(y_species_broad = 
           reorder(
             factor(paste0(species_broad, "\n", "(n = ", n, ")")),
             as.numeric(species_broad)
           )) %>%
  ggplot(aes(y= y_species_broad, fill=circumstances_bite_broad, pattern = circumstances_bite_broad_intended)) + 
  ggpattern::geom_bar_pattern(
    colour = "black",
    position = "fill",
    pattern_density = 0.1,
    pattern_spacing = 0.015,
    pattern_key_scale_factor = 0.9
  ) +
  # ADD
  geom_bar(
    aes(y = as.numeric(y_species_broad)+0.48, colour = circumstances_bite_avoidability),
    width = 0,
    position = "fill",
    fill = NA, size = 2,
    inherit.aes = FALSE,
    key_glyph = draw_key_path
  ) +
  scale_x_continuous(labels=c("0","25%","50%","75%","100%")) +
  labs(x="Proportion of responses", y="Animal species", fill="Circumstances", pattern="Intentional Human Engagement",
       # ADD
       colour = "Avoidability") +
  ggtitle("Circumstances of bite in different animal species") + 
  scale_fill_manual(values =c("Intentional Human Engagement" = "#EC7979",
                              "Unintentional Human Engagement" = "lightblue",
                              "Apparently unprovoked" = "darkblue")) + 
  # ADD
  scale_colour_manual(values = c("Avoidable" = "red", "Unavoidable" = "green3")) +
  ggpattern::scale_pattern_manual(values=c("Threatening Engagement" = "stripe",
                                           "Friendly Engagement" = "circle",
                                           "Practical Engagement" = "crosshatch",
                                           "Unintentional Human Engagement" = "none",
                                           "Apparently unprovoked" = "none"),
                                  breaks = c("Threatening Engagement", "Friendly Engagement", "Practical Engagement") )+
  theme(legend.key = element_rect(fill = "white")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white")),
         fill = guide_legend(override.aes = list(pattern = "none")),
         # ADD
         colour = guide_legend(override.aes = list(size = 5, fill = NA, pattern = "none")))

ggplot with line segments instead of wrapping boundaries

Some notes on this:

  • I use as.numeric(y_species_broad)+0.48 as the y-axis displacement (after coord_flip) so that the line is not centered among the bars; 0.48 does overlap the other bar's top boundary a smidge, play with this.
  • size=2 works well here, but if you change the overall canvas dimensions, one thing to guard against is having the line close enough to the next-bar-above so that it might be mis-attributed to the wrong species.
  • I use key_glyph = draw_key_path (suggested here) to make the legend appear as a line instead of a rectangle; feel free to remove this is you prefer to show a rectangle in the legend and a line in the plot.
  • I initially attempted to use geom_path and geom_segment, and while possible, I was jumping through a few hoops to ensure the order of the cumulative proportions was preserved. This was too fragile, so I'm suggesting for safety that we use a compacted geom_bar instead.

Note that we are no longer occluding/biasing the smaller areas.

enter image description here


Below is my first attempt at this answer, preserved for options, but recognize that it is biasing the visual interpretation.

My first thought was to add aes(colour=..) to the geom_bar_pattern,

sur %>% 
  filter(!(species_broad %in% c("Monkey", "Cattle"))) %>%
  drop_na(circumstances_bite_broad_intended) %>%
  add_count(species_broad) %>%
  mutate(y_species_broad = 
           reorder(
             factor(paste0(species_broad, "\n", "(n = ", n, ")")),
             as.numeric(species_broad)
           )) %>% 
  ggplot(aes(y= y_species_broad, fill=circumstances_bite_broad, pattern = circumstances_bite_broad_intended)) + 
  ggpattern::geom_bar_pattern(
    # colour = "black",
    aes(colour = circumstances_bite_avoidability), size = 2,
    position = "fill",
    pattern_density = 0.1,
    pattern_spacing = 0.015,
    pattern_key_scale_factor = 0.9
  ) +
  scale_x_continuous(labels=c("0","25%","50%","75%","100%")) +
  labs(x="Proportion of responses", y="Animal species", fill="Circumstances", pattern="Intentional Human Engagement",
       colour = "Avoidability") +
  ggtitle("Circumstances of bite in different animal species") + 
  scale_fill_manual(values =c("Intentional Human Engagement" = "#EC7979",
                              "Unintentional Human Engagement" = "lightblue",
                              "Apparently unprovoked" = "darkblue")) + 
  scale_colour_manual(values = c("Avoidable" = "red", "Unavoidable" = "green3")) +
  ggpattern::scale_pattern_manual(values=c("Threatening Engagement" = "stripe",
                                           "Friendly Engagement" = "circle",
                                           "Practical Engagement" = "crosshatch",
                                           "Unintentional Human Engagement" = "none",
                                           "Apparently unprovoked" = "none"),
                                  breaks = c("Threatening Engagement", "Friendly Engagement", "Practical Engagement") )+
  theme(legend.key = element_rect(fill = "white")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white")),
         fill = guide_legend(override.aes = list(pattern = "none")),
         colour = guide_legend(override.aes = list(size = 5, fill = NA, pattern = "none")))

resulting in

ggplot with too many boundary lines

but to remove the intermediate colored lines, I think we should overlay a new geom_bar over the previous, and remove some of the grouping.

sur %>% 
  filter(!(species_broad %in% c("Monkey", "Cattle"))) %>%
  drop_na(circumstances_bite_broad_intended) %>%
  add_count(species_broad) %>%
  mutate(y_species_broad = 
           reorder(
             factor(paste0(species_broad, "\n", "(n = ", n, ")")),
             as.numeric(species_broad)
           )) %>% 
  ggplot(aes(y= y_species_broad, fill=circumstances_bite_broad, pattern = circumstances_bite_broad_intended)) + 
  ggpattern::geom_bar_pattern(
    colour = "black",
    position = "fill",
    pattern_density = 0.1,
    pattern_spacing = 0.015,
    pattern_key_scale_factor = 0.9
  ) +
  # ADD
  geom_bar(
    aes(y = y_species_broad, colour = circumstances_bite_avoidability),
    position = "fill",
    fill = NA, size = 2,
    inherit.aes = FALSE
  ) +
  scale_x_continuous(labels=c("0","25%","50%","75%","100%")) +
  labs(x="Proportion of responses", y="Animal species", fill="Circumstances", pattern="Intentional Human Engagement",
       # ADD
       colour = "Avoidability") +
  ggtitle("Circumstances of bite in different animal species") + 
  scale_fill_manual(values =c("Intentional Human Engagement" = "#EC7979",
                              "Unintentional Human Engagement" = "lightblue",
                              "Apparently unprovoked" = "darkblue")) + 
  # ADD
  scale_colour_manual(values = c("Avoidable" = "red", "Unavoidable" = "green3")) +
  ggpattern::scale_pattern_manual(values=c("Threatening Engagement" = "stripe",
                                           "Friendly Engagement" = "circle",
                                           "Practical Engagement" = "crosshatch",
                                           "Unintentional Human Engagement" = "none",
                                           "Apparently unprovoked" = "none"),
                                  breaks = c("Threatening Engagement", "Friendly Engagement", "Practical Engagement") )+
  theme(legend.key = element_rect(fill = "white")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white")),
         fill = guide_legend(override.aes = list(pattern = "none")),
         # ADD
         colour = guide_legend(override.aes = list(size = 5, fill = NA, pattern = "none")))

ggplot with reduced boundary lines

I made the size of both the bars (2) and the legend (5) a bit large for demonstration, you should edit those to meet your preference.

I suspect the reversal between red and green between your plot and this one is merely an image-editing error?


Data

sur <- structure(list(species_broad = structure(c(1L, 1L, 1L, 2L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 3L, 5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 2L, 2L, 2L, 3L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 3L, 1L, 1L, 1L,  1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 2L, 2L, 3L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 3L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 2L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 3L, 1L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L,  1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 2L, 2L, 3L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 4L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 4L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 3L, 1L, 1L, 3L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 3L, 1L, 3L, 1L), levels = c("Dog", "Cat", "Rodent", "Monkey",  "Cattle"), class = "factor"), circumstances_bite_broad_intended = structure(c(2L, 4L, 1L, 3L, 4L, 4L, 4L, 5L, 5L, 3L, 5L, 1L, 4L, 5L, 5L, 1L, 5L, 5L, 2L, 5L, 4L, 3L, 5L, 2L, 2L, 5L, 4L, 3L, 3L, 5L, 1L, 5L, 1L, 1L, 5L, 5L, 4L, 4L, 5L, 1L, 5L, 5L, 1L, 1L, 5L, 5L, 2L, 3L, 5L, NA, 4L, 5L, 4L, 4L, 4L, 1L, 4L, 1L, 5L, 4L, 5L, 2L, 5L, 1L, 3L, 1L, 2L, 4L, 5L, 5L, 1L, 5L, 5L, 5L, 5L, 5L, 5L, 2L, NA, 1L, 2L, 3L, 5L, 1L, 2L, 3L, 4L, 4L, 4L, 4L, 4L, 3L, 4L, 1L, 4L, 4L, 3L, 4L, 3L, 4L, 3L, 4L, 5L, 4L, 2L, 4L,  3L, 4L, 2L, 4L, 3L, 5L, 1L, 5L, 5L, 3L, 2L, 2L, 5L, 4L, 4L, 4L, 4L, 4L, 3L, 5L, 3L, 4L, 4L, 4L, 1L, 1L, 2L, 4L, 5L, 4L, 4L, 4L, 3L, 4L, 5L, 5L, 4L, 4L, 5L, 5L, 5L, 5L, 4L, 4L, 5L, 4L, 2L, 5L, 5L, 5L, 5L, 3L, 5L, 1L, 5L, 4L, 3L, 5L, 1L, 2L, 2L, 4L, 4L, 4L, 5L, 4L, 5L, 5L, 4L, 2L, 4L, 2L, 4L, 4L, 4L, 4L, 2L, 3L, 2L, 2L, 4L, 4L, 1L, 5L, 4L, 4L, 4L, 4L, 4L, 4L, 1L, 4L, 2L, 4L, 3L, 5L, 5L, 2L, 4L, 1L, 4L, 2L, 4L, 4L, 4L, 4L, 4L, 1L, 1L, 2L, 1L, 4L, 5L, 4L, 4L, 4L, 4L, 4L, 2L, 4L, 4L, 3L, 5L, 4L, 4L, 4L,  4L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 4L, 2L, 3L, 1L, 1L, 4L, 4L, 4L, 4L, 1L, 4L, 4L, 4L, 4L, 5L, 4L, 2L, 4L, 4L, 4L, 4L, 2L, 4L, 4L, 2L, 5L, 5L, 5L, 5L, 4L, 4L, 4L, 5L, 5L, 5L, 4L, 1L, 4L, 4L, 5L, 1L, 5L, 2L, 1L, 2L, 1L, 4L, 4L, 4L, 5L, 2L, 4L, 2L, 4L, 4L, 4L, 2L, 5L, 4L, 4L, 4L, 1L, 4L, 3L, 4L, 4L, 2L, 5L, 2L, 2L, 3L, 5L, 2L, 4L, 5L, 1L, 4L, 4L, 4L, 2L, 4L, 5L, 3L, 2L, 4L, 3L, 4L, 4L, 4L, 5L, 4L, 1L, 4L, 4L, 5L, 4L, 4L, 5L, 4L, 5L, 4L, 4L, 4L, 5L, 2L, 1L, 3L, 5L, 4L, 5L, 5L, 5L, 1L, 5L), levels = c("Threatening Engagement",  "Friendly Engagement", "Practical Engagement", "Unintentional Human Engagement", "Apparently unprovoked"), class = "factor"), circumstances_bite_broad = structure(c(1L, 2L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 1L, 3L, 1L, 2L, 3L, 3L, 1L, 3L, 3L, 1L, 3L, 2L, 1L, 3L, 1L, 1L, 3L, 2L, 1L, 1L, 3L, 1L, 3L, 1L, 1L, 3L, 3L, 2L, 2L, 3L, 1L, 3L, 3L, 1L, 1L, 3L, 3L, 1L, 1L, 3L, NA, 2L, 3L, 2L, 2L, 2L, 1L, 2L, 1L, 3L, 2L, 3L, 1L, 3L, 1L, 1L, 1L, 1L, 2L, 3L, 3L, 1L, 3L, 3L, 3L, 3L, 3L, 3L, 1L, NA, 1L, 1L, 1L, 3L, 1L,  1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 3L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 3L, 1L, 3L, 3L, 1L, 1L, 1L, 3L, 2L, 2L, 2L, 2L, 2L, 1L, 3L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 3L, 2L, 2L, 2L, 1L, 2L, 3L, 3L, 2L, 2L, 3L, 3L, 3L, 3L, 2L, 2L, 3L, 2L, 1L, 3L, 3L, 3L, 3L, 1L, 3L, 1L, 3L, 2L, 1L, 3L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 2L, 3L, 3L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 3L, 3L, 1L, 2L, 1L, 2L, 1L, 2L, 2L,  2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 3L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 3L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 3L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 3L, 3L, 3L, 3L, 2L, 2L, 2L, 3L, 3L, 3L, 2L, 1L, 2L, 2L, 3L, 1L, 3L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 3L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 3L, 1L, 1L, 1L, 3L, 1L, 2L, 3L, 1L, 2L, 2L, 2L, 1L, 2L, 3L, 1L, 1L, 2L, 1L, 2L, 2L, 2L, 3L, 2L, 1L, 2L, 2L, 3L,  2L, 2L, 3L, 2L, 3L, 2L, 2L, 2L, 3L, 1L, 1L, 1L, 3L, 2L, 3L, 3L, 3L, 1L, 3L), levels = c("Intentional Human Engagement", "Unintentional Human Engagement", "Apparently unprovoked"), class = "factor"), circumstances_bite_avoidability = structure(c(1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 2L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 2L, 2L, 1L, 1L, 2L, NA, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 1L,  1L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, NA, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 1L, 2L,  2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 1L, 2L, 1L, 1L, 1L, 2L, 1L, 2L, 2L,  1L, 2L, 2L, 2L, 1L, 2L, 2L, 1L, 1L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 1L, 2L), levels = c("Avoidable", "Unavoidable"), class = "factor")), row.names = c(NA, -355L), class = "data.frame")
r2evans
  • 141,215
  • 6
  • 77
  • 149
  • 1
    @RakeshChand, note that having a boundary does bias the interpretation a little: our eyes tend to see ***AREA*** (of each rectangle) as the proportion of a combination, and adding a boundary does occlude a portion of the area. For instance, see the light-blue in Rodent, it looks much smaller in comparison to dark-blue than when viewed in your original plot without boundaries. It's not difficult to imagine that if the light-blue proportion were smaller, the light-blue itself would not be visible at all. **I retract my statement that adding these boundaries is harmless.** – r2evans Aug 14 '23 at 13:48
  • @RakeshChand see my edit, I think it is worth revisiting. – r2evans Aug 14 '23 at 14:35
  • Definitely, an important issue with the visualization. I like the figure where only the top border is coloured. Other ways to navigate this could be: 1. Not colouring the intermediate lines, i.e. only surround by 3 sides 2. Keeping regular thickness of the borders but with contrasting colours between borders (Avoidability) and filling (Circumstances). For eg: darker colours in borders and light colours in fillings. 3. no border colour but different border patterns. Dotted and Non-dotted lines as borders. What do you think? Are these ideas better? Which one do you recommend? – Rakesh Chand Aug 14 '23 at 15:19
  • 1
    "Contrasting colors" still obscures the fill color, and border patterns can be too sparse or still obscuring as well. The only way to _not_ obscure the inner fill is to not obscure the inner fill, place the boundaries outside of the bars completely. Similarly, "three sides" is reducing but not removing the overlap. I do think that highly-contrasting colors has potential, but ... the default size (that does not obscure) is in my opinion too small to stand out like I think you want. I prefer the top-lines, personally, it breaks out what you need with zero overlap. – r2evans Aug 14 '23 at 15:23
  • 1
    I will use the top-line method now. Will also talk to my supervisor about the figure regarding what are his views. Thank you so much for your insight and amazing help with the figure. You have no idea how helpful you have been. – Rakesh Chand Aug 14 '23 at 15:34