R - nudge only selected values and keep others static with geom_text_repel
Asked Answered
D

1

9

I want to use geom_text_repel to have my labels lie as close to the edge of a pie graph as possible unless the percent is under a certain value, in which case the label should be nudged further away and connected with a line. I adapted a solution from Move labels in ggplot2 pie graph but increased the xpos values for groups above the threshold.

library(dplyr)
library(ggplot2)
library(ggrepel)
library(scales)
threshold = 0.05    
age <- data.frame(Age = c("20 - 29", "30 - 39", "40 - 49", "50 - 59", "60 - 69"), count = c(27, 29, 26, 16, 2))
age <- age %>% mutate(percent = count/sum(count),
            cs = rev(cumsum(rev(percent))),
            ypos = percent/2 + lead(cs, 1),
            ypos = ifelse(is.na(ypos), percent/2, ypos),
            xpos = ifelse(percent > threshold, 1.8, 1.3),
            xn = ifelse(percent > threshold, 0, 0.5))
ggplot(age, aes_string(x = 1, y = "percent", fill = "Age")) +
    geom_bar(width = 1 , stat = "identity", colour = "black") +
    geom_text_repel(aes(label = percent(percent, accuracy = 0.1), x = xpos, y = ypos), size = 7.5, nudge_x = age$xn, segment.size = .5, direction = "x", force = 0.5, hjust = 1) +
    coord_polar("y" , start = 0, clip = "off") + 
    theme_minimal() +
    theme(axis.text.x = element_blank(),
          axis.title.x = element_blank(),
          axis.text.y = element_blank(),
          axis.title.y = element_blank(),
          panel.border = element_blank(),
          panel.grid = element_blank(),
          legend.title = element_text(size = 22.5),
          legend.text = element_text(size = 19.5),
          legend.box.margin=margin(c(0,0,0,30))) +
    labs(fill = "Age") +
    scale_fill_manual(values = c("#2B83BA", "#FDAE61", "#FFFF99", "#ABDDA4", "#D7191C"))

enter image description here

The below-threshold values act as expected, but the above-threshold values seem to vary in how far they're located from the edge. I believe two things are at play:

  1. The labels are still being "repelled" despite not being that close to any other labels. This is most evident with the 16.0% label.
  2. The xpos dictates the position of the centre of the label, but since the labels are horizontal, they might cut into the graph if the label's positioning is to close to the horizontal axis.

How can I account for these two issues? Or if there any any other issues I'd appreciate help in identifying them. I would consider the 29.0% label to be good enough, if others could follow that format.

Dibromide answered 18/1, 2021 at 22:16 Comment(2)
Does the accepted solution here produce a different outcome? :) #48185145Roasting
hey @JonnyPhelps, I've actually used that solution in previous implementations. The problem is that if small values were too close to each other, they would overlap. I'm hoping to make use of geom_text_repel to automatically space out the labels in a consistent way so that all graphs have a similar appearance no matter what the values areDibromide
M
2

I would offer the following tricks:

  1. To overcome the first issue, use both ofgeom_text_repel() and geom_text() for all data, but show the label in geom_text_repel() only for values less than threshold, and show label in geom_text() only for values more than threshold.

  2. To overcome the second issue, use hjust = 'outward' in geom_text(), and adjust the value of nudge_x both in geom_text() and geom_text_repel().

  3. Use geom_segment() to create lines connecting pie chart areas with the labels.

Here is the full code:

library(dplyr)
library(ggplot2)
library(ggrepel)
library(scales)
threshold = 0.05    
age <- data.frame(Age = c("20 - 29", "30 - 39", "40 - 49", "50 - 59", "60 - 69"), count = c(27, 29, 26, 16, 2))
age <- age %>% mutate(percent = count/sum(count),
                      cs = rev(cumsum(rev(percent))),
                      ypos = percent/2 + lead(cs, 1),
                      ypos = ifelse(is.na(ypos), percent/2, ypos),
                      xpos = ifelse(percent > threshold, 1.4, 1.8))
ggplot(age, aes_string(x = 1, y = "percent", fill = "Age")) +
    geom_bar(width = 1 , stat = "identity", colour = "black") +
    theme_minimal() +
    theme(axis.text.x = element_blank(),
          axis.title.x = element_blank(),
          axis.text.y = element_blank(),
          axis.title.y = element_blank(),
          panel.border = element_blank(),
          panel.grid = element_blank(),
          legend.title = element_text(size = 22.5),
          legend.text = element_text(size = 19.5),
          legend.box.margin=margin(c(0,0,0,30))) +
    labs(fill = "Age") +
    scale_fill_manual(values = c("#2B83BA", "#FDAE61", "#FFFF99", "#ABDDA4", "#D7191C")) + 
    geom_segment(aes(x = ifelse(percent<threshold,1, xpos), xend = xpos, y = ypos, yend = ypos)) + 
    geom_text(aes(x = xpos, y = ypos, label = ifelse(percent>threshold,percent(percent, accuracy = 0.1),"")), hjust = "outward", nudge_x  = 0.2, size = 7.5) + 
    geom_text_repel(aes(x = xpos, y = ypos, label = ifelse(percent<threshold, percent(percent, accuracy = 0.1), "")), nudge_x  = 0.2, size = 7.5)+ 
    coord_polar("y")

enter image description here

I have tried this code for more than one values less than threshold by adjusting nudge_x, and it works. For example:

library(dplyr)
library(ggplot2)
library(ggrepel)
library(scales)
threshold = 0.05    
age <- data.frame(Age = c("20 - 29", "30 - 39", "40 - 49", "50 - 59", "60 - 69"), count = c(50, 44, 1, 2, 3))
age <- age %>% mutate(percent = count/sum(count),
                      cs = rev(cumsum(rev(percent))),
                      ypos = percent/2 + lead(cs, 1),
                      ypos = ifelse(is.na(ypos), percent/2, ypos),
                      xpos = ifelse(percent > threshold, 1.4, 1.8))
ggplot(age, aes_string(x = 1, y = "percent", fill = "Age")) +
    geom_bar(width = 1 , stat = "identity", colour = "black") +
    theme_minimal() +
    theme(axis.text.x = element_blank(),
          axis.title.x = element_blank(),
          axis.text.y = element_blank(),
          axis.title.y = element_blank(),
          panel.border = element_blank(),
          panel.grid = element_blank(),
          legend.title = element_text(size = 22.5),
          legend.text = element_text(size = 19.5),
          legend.box.margin=margin(c(0,0,0,30))) +
    labs(fill = "Age") +
    scale_fill_manual(values = c("#2B83BA", "#FDAE61", "#FFFF99", "#ABDDA4", "#D7191C")) + 
    geom_segment(aes(x = ifelse(percent<threshold,1, xpos), xend = xpos, y = ypos, yend = ypos)) + 
    geom_text(aes(x = xpos, y = ypos, label = ifelse(percent>threshold,percent(percent, accuracy = 0.1),"")), hjust = "outward", nudge_x  = 0.2, size = 7.5) + geom_text_repel(aes(x = xpos, y = ypos, label = ifelse(percent<threshold, percent(percent, accuracy = 0.1), "")), nudge_x  = 0.5, size = 7.5)+ 
    coord_polar("y")

enter image description here

Mongol answered 23/1, 2021 at 2:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.