rggplot2polygonedgesgeom-segment

Adding segments to a geom_polygon ggplot


I have a data.frame where each row defines a composition of a group, and I would like to plot them all as pie charts on a cartesian plane.

Here is the example composition data.frame:

library(dplyr)
set.seed(1)
composition.df <- data.frame(x = runif(10, 0, 100), y = runif(10, 0, 100),
                             y.a_1 = as.integer(runif(10, 0, 100)), y.a_2 = as.integer(runif(10, 0, 100)), y.a_3 = as.integer(runif(10, 0, 100)), y.a_4 = as.integer(runif(10, 0, 100)),
                             y.b_1 = as.integer(runif(10, 0, 100)), y.b_2 = as.integer(runif(10, 0, 100)), y.b_3 = as.integer(runif(10, 0, 100)), y.b_4 = as.integer(runif(10, 0, 100)),
                             o.a_1 = as.integer(runif(10, 0, 100)), o.a_3 = as.integer(runif(10, 0, 100)), o.a_4 = as.integer(runif(10, 0, 100)),
                             o.b_1 = as.integer(runif(10, 0, 100)), o.b_3 = as.integer(runif(10, 0, 100)), o.b_4 = as.integer(runif(10, 0, 100))) %>%
  dplyr::mutate(size = runif(10, 0.002, 0.01)*(y.a_1 + y.a_2 + y.a_3 + y.a_4 + y.b_1 + y.b_2 + y.b_3 + y.b_4 + o.a_1 + o.a_3 + o.a_4 + o.b_1 + o.b_3 + o.b_4))

Each group/pie is assigned to one of three classes:

composition.df$class <- sample(c("A", "B", "C"), nrow(composition.df), replace = T)

In addition, some of the groups/pies are connected by edges/segments. Here I'm creating an example edges data.frame:

edge.idx.df <- t(combn(1:nrow(composition.df), 2)) %>% as.data.frame() %>%
  filter(V1 != V2) %>%
  sample_n(20)

edge.df <- do.call(rbind, lapply(1:nrow(edge.idx.df), function(i)
  data.frame(xstart = composition.df$x[edge.idx.df$V1[i]], ystart = composition.df$y[edge.idx.df$V1[i]], xend = composition.df$x[edge.idx.df$V2[i]], yend = composition.df$y[edge.idx.df$V2[i]])
)) %>% mutate(weight = runif(nrow(edge.idx.df), 0, 1))

In order to plot composition.df as pie charts I'm following this post, where I'm converting composition.df to a polygons data.frame:

composition2polygon <- function(x, y, size, samples, n, rownum, class)
{
  angles <- c(0,2*pi*cumsum(n)/sum(n))
  do.call("rbind",Map(function(a1,a2,s){
    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,sample=s,rownum=rownum,class=class)
  }, head(angles,-1),tail(angles,-1),samples))
}

polygon.df <- composition.df %>%
  mutate(r = row_number()) %>%
  rowwise() %>%
  group_map(~ with(.x, composition2polygon(x, y,
                                           size, c("y.a_1", "y.a_2", "y.a_3", "y.a_4", "y.b_1", "y.b_2", "y.b_3", "y.b_4", "o.a_1", "o.a_3", "o.a_4", "o.b_1", "o.b_3", "o.b_4"),
                                           c(y.a_1, y.a_2, y.a_3, y.a_4, y.b_1, y.b_2, y.b_3, y.b_4, o.a_1, o.a_3, o.a_4, o.b_1, o.b_3, o.b_4), r, class))) %>%
  bind_rows() %>%
  mutate(animal = gsub("\\.a","",sample) %>% gsub("\\.b","",.)) %>% unique()

And setting the sample, animal, and class columns as factors in order to color code them

polygon.df$sample <- factor(polygon.df$sample, levels = c("y.a_1", "y.a_2", "y.a_3", "y.a_4", "y.b_1", "y.b_2", "y.b_3", "y.b_4", "o.a_1", "o.a_3", "o.a_4", "o.b_1", "o.b_3", "o.b_4"))
polygon.df$animal <- factor(polygon.df$animal, levels = c("y_1", "y_2", "y_3", "y_4", "o_1", "o_3", "o_4"))
polygon.df$population <- factor(gsub("^y\\.","",polygon.df$sample) %>% gsub("^o\\.","",.) %>% gsub("_\\d+$","",.),levels=c("a","b"))
polygon.df$class <- factor(polygon.df$class,levels=c("A","B","C"))

Plotting only the polygons works fine:

library(ggplot2)
ggplot(polygon.df, aes(x, y, color = class, fill = animal, group = interaction(sample, rownum))) +
  geom_polygon(linewidth = 1, linetype = "dotted") +
  scale_fill_manual(values = c(y_1="#66C2A5", y_2="#FC8D62", y_3="#8DA0CB", y_4="#E78AC3", o_1="#A6D854", o_3="#FFD92F", o_4="#E5C494")) +
  scale_color_manual(values = c(A="blue", B="red", C="green")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white"))) + coord_equal() + theme_void()

enter image description here

But if I try to add the edges with this:

ggplot(polygon.df, aes(x, y, color = class, fill = animal, group = interaction(sample, rownum))) +
  geom_polygon(linewidth = 1, linetype = "dotted") +
  geom_segment(aes(x = edge.df$xstart, y = edge.df$ystart, xend = edge.df$xend, yend = edge.df$yend),
               color = "darkgray", linewidth = edge.df$weight, alpha = 0.1, inherit.aes = F) +
  scale_fill_manual(values = c(y_1="#66C2A5", y_2="#FC8D62", y_3="#8DA0CB", y_4="#E78AC3", o_1="#A6D854", o_3="#FFD92F", o_4="#E5C494")) +
  scale_color_manual(values = c(A="blue", B="red", C="green")) +
  guides(pattern = guide_legend(override.aes = list(fill = "white"))) + coord_equal() + theme_void()

I get this error:

Error in `geom_segment()`:
! Problem while computing aesthetics.
ℹ Error occurred in the 2nd layer.
Caused by error in `check_aesthetics()`:
! Aesthetics must be either length 1 or the same as the data (4340)
✖ Fix the following mappings: `x`, `y`, `xend`, and `yend`
Run `rlang::last_trace()` to see where the error occurred.

Any idea how to add the edges?


Solution

  • Instead of passing the columns to the aesthetics using edge.df$... , use the data= argument and get rid of edge.df$...:

    library(ggplot2)
    
    ggplot(
      polygon.df,
      aes(x, y,
        color = class, fill = animal,
        group = interaction(sample, rownum)
      )
    ) +
      geom_polygon(linewidth = 1, linetype = "dotted") +
      geom_segment(
        data = edge.df,
        aes(
          x = xstart, y = ystart, xend = xend, yend = yend,
          linewidth = weight
        ),
        color = "darkgray", alpha = 0.1, inherit.aes = FALSE
      ) +
      scale_fill_manual(
        values = c(
          y_1 = "#66C2A5", y_2 = "#FC8D62",
          y_3 = "#8DA0CB", y_4 = "#E78AC3",
          o_1 = "#A6D854", o_3 = "#FFD92F",
          o_4 = "#E5C494"
        )
      ) +
      scale_color_manual(
        values = c(A = "blue", B = "red", C = "green")
      ) +
      coord_equal() +
      theme_void()
    

    enter image description here