1

I'm using the R scatterpie package for plotting pie charts on an xy plane. Each one of the pies/groups also has a class assignment which I would also like to convey in the plot and was thinking of using patterns for that.

Here's the example data.frame:

library(ggplot2)
library(ggpattern)
library(scatterpie)
library(dplyr)
set.seed(1)
df <- data.frame(x = runif(50, 0, 100), y = runif(50, 0, 100), id = paste0("id",1:50),
                 g1 = as.integer(runif(50, 0, 100)), g2 = as.integer(runif(50, 0, 100)), g3 = as.integer(runif(50, 0, 100)),
                 class = sample(c("c1", "c2"), 50, replace = T)) %>%
  dplyr::mutate(size = 0.02*(g1 + g2 + g3))
df$class <- factor(df$class)

Here's the code I tried with the ggpattern package:

ggplot() + geom_scatterpie(data = df, aes(x = x, y = y, group = id, r = size),cols = paste0("g",1:3),color = "black", alpha = 0.8, size = 0.1) +
  scale_fill_manual(values=c("#FFC9F8", "#ee9e77", "#895A44")) +
  geom_col_pattern(aes(pattern_fill = class))+
  xlim(-10,110) + ylim(-10,110) + coord_equal() + theme_void()

Which gives the error:

Don't know how to automatically pick scale for object of type function. Defaulting to continuous.
Error in `f()`:
! Aesthetics must be valid data columns. Problematic aesthetic(s): pattern_fill = class. 
Did you mistype the name of a data column or forget to add after_stat()?

I tried playing around with the pattern part of the command but with no success.

Currently, the only way I am able to encode the class of each pie is with the linetype aesthetics argument:

ggplot() + geom_scatterpie(data = df, aes(x = x, y = y, group = id, r = size, linetype = class),cols = paste0("g",1:3), color = "black", size = 1) +
  scale_fill_manual(values=c("#FFC9F8", "#ee9e77", "#895A44")) +
  xlim(-10,110) + ylim(-10,110) + coord_equal() + theme_void()

enter image description here

But it's not ideal.

Any idea how to make this work or alternative ways to encode class?

user1701545
  • 5,706
  • 14
  • 49
  • 80
  • 1
    I think it is unlikely you'll be able to make those two packages work together. You'd need a `geom_scatterpie_pattern`, which doesn't exist. – Axeman Aug 29 '23 at 18:15
  • Right. Any suggestion on alternative ways of how to convey the `class` of each pie? – user1701545 Aug 29 '23 at 18:17
  • 1
    You could make the three classes different colors, then map `type` to the alpha channel to get three shades of each color. – Axeman Aug 29 '23 at 18:22
  • I sort of understand what you're saying but don't know how to implement the `map type to the alpha channel` part of your comment. Can I ask you to write it down? – user1701545 Aug 29 '23 at 18:26
  • 1
    Ok looking at `geom_scatterpie` in more detail, it violates all the design principles of ggplot, so my idea doesn't really work, sorry. – Axeman Aug 29 '23 at 18:30
  • 1
    Some thoughts, and while it generates an image (and does not error), I don't think it's what you're going for. (1) Because you're only passing data to `geom_scatterpie`, the NSE of `pattern_fill=class` does not have `df` to find its column named `class`, but if finds instead the primitive function `class(.)`, which is obviously wrong. Add `df` to `ggplot(df)`. (2) `geom_col_pattern` also needs `x=` and `y=`, also only provided to `geom_scatterpie`, I suggest `ggplot(df, aes(x=x, y=y))`. Unfortunately scatterpie still needs them as well, back to Axeman's comment about violations. – r2evans Aug 29 '23 at 18:52
  • And **subjectively** ... I find your suggested plot above to be _really difficult to interpret_ as a whole. The add dimension within each pie makes it nearly impossible to do a "mile stare" and see the whole relationship, all I can really see without looking at each pie is x, y, and radius; that might be enough, at which point if the plot is as much for lookup, then the user can then drill down to look at one or two pies ... but even then, it's nearly impossible to do comparison between two (or more!) pies, so it is inching towards "too much data, just for fun", drowning the viewer. – r2evans Aug 29 '23 at 18:55
  • 1
    @r2evans I agree completely. The visualization is possible (see below), but is difficult to interpret. – Allan Cameron Aug 29 '23 at 20:15

1 Answers1

3

I'm not convinced about the visualization, though it is possible via ggplot and ggpattern. I don't think you can use geom_scatterpie, which, as Axeman points out, doesn't really work the way other ggplot geoms do.

If I had to do this, I would probably just make my own pie function and draw the result with geom_polygon_pattern.

You could do that with the following helper function:

make_pie <- function(x, y, size, groups, n, class, rownum) {
  angles <- c(0, 2*pi * cumsum(n)/sum(n))
  do.call("rbind", Map(function(a1, a2, g) {
    xvals <- c(0, sin(seq(a1, a2, len = 30)) * size, 0) + x
    yvals <- c(0, cos(seq(a1, a2, len = 30)) * size, 0) + y
    data.frame(x = xvals, y = yvals, group = g, class = class, rownum = rownum)
  }, head(angles, -1), tail(angles, -1), groups))
}

You can then plot using your data as follows:

df %>%
  mutate(r = row_number()) %>%
  rowwise() %>%
  group_map(~ with(.x, make_pie(x, y, size, c("g1", "g2", "g3"),
                                c(g1, g2, g3), class, r))) %>%
  bind_rows() %>%
  ggplot(aes(x, y, fill = group, group = interaction(group, rownum))) +
  geom_polygon_pattern(aes(pattern = class), pattern_fill = "black",
                       pattern_angle = 45, pattern_spacing = 0.01,
                       pattern_density = 0.1) +
  scale_fill_manual(values = c("#FFC9F8", "#ee9e77", "#895A44")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white"))) +
  coord_equal() +
  theme_void()

enter image description here

You could map the alpha channel as Axeman suggests like this:

df %>%
  mutate(r = row_number()) %>%
  rowwise() %>%
  group_map(~ with(.x, make_pie(x, y, size, c("g1", "g2", "g3"),
                                c(g1, g2, g3), class, r))) %>%
  bind_rows() %>%
  ggplot(aes(x, y, fill = class, group = interaction(group, rownum),
             alpha = group)) +
  geom_polygon() +
  scale_fill_manual(values = c("red4", "green4")) +
  scale_alpha_manual(values = c(0.3, 0.7, 1)) +
  coord_equal() +
  theme_void()

enter image description here

Or just declare each fill color individually:

df %>%
  mutate(r = row_number()) %>%
  rowwise() %>%
  group_map(~ with(.x, make_pie(x, y, size, c("g1", "g2", "g3"),
                                c(g1, g2, g3), class, r))) %>%
  bind_rows() %>%
  mutate(fill_class = paste0(group, " (class ", class, ")")) %>%
  mutate(fill_class = factor(fill_class, unique(fill_class))) %>%
  ggplot(aes(x, y, fill = fill_class, group = interaction(group, rownum))) +
  geom_polygon() +
  scale_fill_manual("Group (class)",
                    values = c("red3", "hotpink", "red", 
                               "navy", "blue", "dodgerblue")) +
  coord_equal() +
  theme_void()

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • 2
    +1 for making this work; +1 for coming up with a replacement for `geom_scatterpie` (though it can and should be fixed); and +1 for the last plot that is a vast improvement on the use of patterns. Bravo! – r2evans Aug 29 '23 at 20:31
  • Great solution. I wouldn't worry too much about the interpretability of this example dataset. – user1701545 Aug 29 '23 at 22:53
  • Well done! The colored variants are a nice improvement over the patterns. – Axeman Aug 30 '23 at 04:04