Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

graph

r

geom-text

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.

like image 562
Nicholas Hassan Avatar asked Jan 18 '21 22:01

Nicholas Hassan


People also ask

What is the use of text geom in R?

Text Text geoms are useful for labeling plots. They can be used by themselves as scatterplots or in combination with other geoms, for example, for labeling points or for annotating the height of bars. geom_text () adds only text to the plot. geom_label () draws a rectangle behind the text, making it easier to read.

How to use Geom_text () and Geom_label ()?

They can be used by themselves as scatterplots or in combination with other geoms, for example, for labeling points or for annotating the height of bars. geom_text () adds only text to the plot. geom_label () draws a rectangle behind the text, making it easier to read. Set of aesthetic mappings created by aes () or aes_ ().

What does position_nudge do in Geom_text?

position_nudge is generally useful for adjusting the position of items on discrete scales by a small amount. Nudging is built in to geom_text() because it's so useful for moving labels a small distance from what they're labelling. position_nudge(x = 0, y = 0) Amount of vertical and horizontal distance to move.

Should data be arranged by the label column before calling Geom_text ()?

Therefore data should be arranged by the label column before calling geom_text (). Note that this argument is not supported by geom_label (). Note that when you resize a plot, text labels stay the same size, even though the size of the plot area changes. This happens because the "width" and "height" of a text element are 0.


1 Answers

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

like image 119
Abdur Rohman Avatar answered Oct 16 '22 11:10

Abdur Rohman