ggrepel icon indicating copy to clipboard operation
ggrepel copied to clipboard

Multiple limits for ggrepel in ggplot

Open marcopm94 opened this issue 4 years ago • 7 comments

I have a graph with data similar to this.

data <- data.frame(x = c(1:30),
                   y = rnorm(30, 0, 1),
                   z = rep(c("A", "B", "C"), 10))

ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(aes(label = z), force = 1, segment.alpha = .5, box.padding = 1, xlim = c(-5, 35), ylim = c(-5, 5)) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

image

I am trying to get the labels to be contained a the borders of the graph, so on the x-axis they are always either >2.5 or <-2.5 and on the y-axis they are always either >30 or <-30. I know the xlim() and ylim() features can put the limits within one rectangle, but how could I create multiple limits so that they stay on the edges of the graph? Thank you!

marcopm94 avatar Apr 09 '20 12:04 marcopm94

Thanks for opening a new issue.

If I understand you correctly, you're asking if ggrepel supports excluding text labels from a rectangular region. Unfortunately, ggrepel does not support this feature. This might be an interesting new feature to consider for the future.

You might consider working around the limitations of ggrepel. One approach is to divide the data into multiple pieces. Then each piece could be limited to one rectangle.

library(ggrepel)
#> Loading required package: ggplot2

data <- data.frame(
  x = c(1:30),
  y = rnorm(30, 0, 1),
  z = rep(c("A", "B", "C"), 10)
)

d1 <- data[data$y > quantile(data$y, 0.5),]
d2 <- data[data$y <= quantile(data$y, 0.5),]

ggplot() +
  aes(x = x, y = y, group = z, color = z, label = z) +
  geom_point(data = d1) +
  geom_point(data = d2) +
  geom_label_repel(
    data = d1,
    segment.alpha = .5,
    ylim = c(2.5, Inf)
  ) +
  geom_label_repel(
    data = d2,
    segment.alpha = .5,
    ylim = c(-2.5, -Inf)
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

Created on 2020-04-09 by the reprex package (v0.3.0)

Session info
devtools::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value                       
#>  version  R version 3.6.1 (2019-07-05)
#>  os       macOS Catalina 10.15.4      
#>  system   x86_64, darwin15.6.0        
#>  ui       X11                         
#>  language (EN)                        
#>  collate  en_US.UTF-8                 
#>  ctype    en_US.UTF-8                 
#>  tz       America/New_York            
#>  date     2020-04-09                  
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version    date       lib source                        
#>  assertthat    0.2.1      2019-03-21 [1] CRAN (R 3.6.0)                
#>  backports     1.1.6      2020-04-05 [1] CRAN (R 3.6.1)                
#>  callr         3.4.3      2020-03-28 [1] CRAN (R 3.6.2)                
#>  cli           2.0.2      2020-02-28 [1] CRAN (R 3.6.0)                
#>  colorspace    1.4-1      2019-03-18 [1] CRAN (R 3.6.0)                
#>  crayon        1.3.4      2017-09-16 [1] CRAN (R 3.6.0)                
#>  curl          4.3        2019-12-02 [1] CRAN (R 3.6.1)                
#>  desc          1.2.0      2018-05-01 [1] CRAN (R 3.6.0)                
#>  devtools      2.2.2      2020-02-17 [1] CRAN (R 3.6.0)                
#>  digest        0.6.25     2020-02-23 [1] CRAN (R 3.6.0)                
#>  dplyr         0.8.5      2020-03-07 [1] CRAN (R 3.6.0)                
#>  ellipsis      0.3.0      2019-09-20 [1] CRAN (R 3.6.0)                
#>  evaluate      0.14       2019-05-28 [1] CRAN (R 3.6.0)                
#>  fansi         0.4.1      2020-01-08 [1] CRAN (R 3.6.1)                
#>  farver        2.0.3      2020-01-16 [1] CRAN (R 3.6.0)                
#>  fs            1.4.1      2020-04-04 [1] CRAN (R 3.6.1)                
#>  ggplot2     * 3.3.0      2020-03-05 [1] CRAN (R 3.6.0)                
#>  ggrepel     * 0.8.2      2020-03-08 [1] CRAN (R 3.6.0)                
#>  glue          1.4.0      2020-04-03 [1] CRAN (R 3.6.1)                
#>  gtable        0.3.0      2019-03-25 [1] CRAN (R 3.6.0)                
#>  highr         0.8        2019-03-20 [1] CRAN (R 3.6.0)                
#>  htmltools     0.4.0      2019-10-04 [1] CRAN (R 3.6.0)                
#>  httr          1.4.1      2019-08-05 [1] CRAN (R 3.6.0)                
#>  knitr         1.28       2020-02-06 [1] CRAN (R 3.6.0)                
#>  labeling      0.3        2014-08-23 [1] CRAN (R 3.6.0)                
#>  lifecycle     0.2.0      2020-03-06 [1] CRAN (R 3.6.0)                
#>  magrittr      1.5        2014-11-22 [1] CRAN (R 3.6.0)                
#>  memoise       1.1.0.9000 2020-01-12 [1] Github (r-lib/memoise@58d3972)
#>  mime          0.9        2020-02-04 [1] CRAN (R 3.6.0)                
#>  munsell       0.5.0      2018-06-12 [1] CRAN (R 3.6.0)                
#>  pillar        1.4.3      2019-12-20 [1] CRAN (R 3.6.0)                
#>  pkgbuild      1.0.6      2019-10-09 [1] CRAN (R 3.6.0)                
#>  pkgconfig     2.0.3      2019-09-22 [1] CRAN (R 3.6.1)                
#>  pkgload       1.0.2      2018-10-29 [1] CRAN (R 3.6.0)                
#>  prettyunits   1.1.1      2020-01-24 [1] CRAN (R 3.6.0)                
#>  processx      3.4.2      2020-02-09 [1] CRAN (R 3.6.1)                
#>  ps            1.3.2      2020-02-13 [1] CRAN (R 3.6.0)                
#>  purrr         0.3.3      2019-10-18 [1] CRAN (R 3.6.0)                
#>  R6            2.4.1      2019-11-12 [1] CRAN (R 3.6.0)                
#>  Rcpp          1.0.4.6    2020-04-07 [1] Github (RcppCore/Rcpp@a831639)
#>  remotes       2.1.1      2020-02-15 [1] CRAN (R 3.6.0)                
#>  rlang         0.4.5      2020-03-01 [1] CRAN (R 3.6.1)                
#>  rmarkdown     2.1        2020-01-20 [1] CRAN (R 3.6.0)                
#>  rprojroot     1.3-2      2018-01-03 [1] CRAN (R 3.6.0)                
#>  scales        1.1.0      2019-11-18 [1] CRAN (R 3.6.0)                
#>  sessioninfo   1.1.1      2018-11-05 [1] CRAN (R 3.6.0)                
#>  stringi       1.4.6      2020-02-17 [1] CRAN (R 3.6.0)                
#>  stringr       1.4.0      2019-02-10 [1] CRAN (R 3.6.0)                
#>  testthat      2.3.2      2020-03-02 [1] CRAN (R 3.6.0)                
#>  tibble        3.0.0      2020-03-30 [1] CRAN (R 3.6.2)                
#>  tidyselect    1.0.0      2020-01-27 [1] CRAN (R 3.6.0)                
#>  usethis       1.5.1      2019-07-04 [1] CRAN (R 3.6.0)                
#>  vctrs         0.2.4      2020-03-10 [1] CRAN (R 3.6.1)                
#>  withr         2.1.2      2018-03-15 [1] CRAN (R 3.6.0)                
#>  xfun          0.12       2020-01-13 [1] CRAN (R 3.6.0)                
#>  xml2          1.3.0      2020-04-01 [1] CRAN (R 3.6.2)                
#>  yaml          2.2.1      2020-02-01 [1] CRAN (R 3.6.0)                
#> 
#> [1] /Library/Frameworks/R.framework/Versions/3.6/Resources/library

slowkow avatar Apr 09 '20 13:04 slowkow

If you want to spread the labels to all four borders, then you could use nudge_x, nudge_y and direction

  1. position the labels in the nearst corner (abusing nudge as an aesthetic):
set.seed(0)
ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf),
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      force = 0, # note: zero force for demonstration
      segment.alpha = .5, box.padding = 1
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

image

  1. restrict in which direction specific labels are allowed to move (by ways of splitting the data) and increase the force to your liking
  2. set ylim on the part which is restricted to the y direction, or xlim on the part which is restricted to the x direction, to prevent overlapping labels in the corners.

Result:

set.seed(0)

ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(
      data=data[abs(scale(data$y)) > abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf),
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      direction='x',
      force = 2,
      segment.alpha = .5,
      box.padding = 1
  ) +
  geom_label_repel(
      data=data[abs(scale(data$y)) < abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf),
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      direction='y',
      force = 25,
      segment.alpha = .5,
      box.padding = 1,
      ylim = c(-4.5, 4.5),
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

image

A few notes:

  • if direction accepted a vector, we would not need to split the data, nor set the xlim/ylim (even better if this was an aesthetic)
  • I am getting “Ignoring unknown aesthetics: nudge_y, nudge_x” warning, but they appear to work. (ggplot 3.3.0, ggrepel from master)

krassowski avatar Apr 09 '20 16:04 krassowski

Or maybe even better, only nudge in the direction of interest:

set.seed(0)

ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(
      data=data[abs(scale(data$y)) > abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf)
      ),
      direction='x',
      force = 2,
      segment.alpha = .5,
      box.padding = 1
  ) +
  geom_label_repel(
      data=data[abs(scale(data$y)) < abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      direction='y',
      force = 25,
      segment.alpha = .5,
      box.padding = 1,
      ylim = c(-4.5, 4.5),
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

image

krassowski avatar Apr 09 '20 16:04 krassowski

@krassowski Those graphs look perfect. The only problem is that I have copy and pasted the code but I get a different graph:


data <- data.frame(
  x = c(1:30),
  y = rnorm(30, 0, 1),
  z = rep(c("A", "B", "C"), 10)
)


set.seed(0)
ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf),
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      force = 0, # note: zero force for demonstration
      segment.alpha = .5, box.padding = 1
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

set.seed(0)

ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(
      data=data[abs(scale(data$y)) > abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf),
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      direction='x',
      force = 2,
      segment.alpha = .5,
      box.padding = 1
  ) +
  geom_label_repel(
      data=data[abs(scale(data$y)) < abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf),
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      direction='y',
      force = 25,
      segment.alpha = .5,
      box.padding = 1,
      ylim = c(-4.5, 4.5),
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

set.seed(0)

ggplot(data = data, aes(x = x, y = y, group = z, color = z, label = z)) +
  geom_point() +
  geom_label_repel(
      data=data[abs(scale(data$y)) > abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_y=ifelse(scale(y) > 0, Inf, -Inf)
      ),
      direction='x',
      force = 2,
      segment.alpha = .5,
      box.padding = 1
  ) +
  geom_label_repel(
      data=data[abs(scale(data$y)) < abs(scale(data$x)), ],
      aes(
          label=z,
          nudge_x=ifelse(scale(x) > 0, Inf, -Inf)
      ),
      direction='y',
      force = 25,
      segment.alpha = .5,
      box.padding = 1,
      ylim = c(-4.5, 4.5),
  ) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5))

image image image

Could it be some problem in the installation of the package, or maybe the code didn't copy right on the site?

marcopm94 avatar Apr 09 '20 16:04 marcopm94

I think that it is because I use a newer (yet to be released) version. Sorry, I did not check if it works with the latest released version. If you are not afraid you could try:

devtools::install_github("slowkow/ggrepel")
# a restart of the session might be required

but, this is not a stable release yet and may have some rough edges.

krassowski avatar Apr 09 '20 16:04 krassowski

Solved! I have tweeked the code by @krassowski and now it works for my session, thank you! I downloaded the newer version and restarted but the output was the same. With some minor changes I got this:


set.seed(0)

data <- data.frame(
  x = c(1:30),
  y = rnorm(30, 0, 1),
  z = rep(c("A", "B", "C"), 10)
)

data$g <- as.factor(ifelse(abs(scale(data$y)) > abs(scale(data$x))
                           & scale(data$y) > 0, 1,
                    ifelse(abs(scale(data$y)) > abs(scale(data$x))
                           & scale(data$y) < 0, 2,
                    ifelse(abs(scale(data$x)) > abs(scale(data$y))
                           & scale(data$x) > 0, 3,
                    ifelse(abs(scale(data$x)) > abs(scale(data$y))
                           & scale(data$x) < 0, 4, NA)))))

ggplot(data = data, aes(x = x, y = y, group = z, color = g, label = z)) +
  geom_point() +
  geom_label_repel(data=data[data$g == 1, ],
                   aes(label=z), nudge_y = 10, ylim = c(-5, 5),
                   segment.alpha = .5, box.padding = 0.8, xlim = c(-5, 35)) +
  geom_label_repel(data=data[data$g == 2, ],
                   aes(label=z), nudge_y = -10, ylim = c(-5, 5),
                   segment.alpha = .5, box.padding = 0.8, xlim = c(-5, 35)) +
  geom_label_repel(data=data[data$g == 3, ],
                   aes(label=z), nudge_x = 10, ylim = c(-5, 5),
                   segment.alpha = .5, box.padding = 0.8, xlim = c(-5, 35)) +
  geom_label_repel(data=data[data$g == 4, ],
                   aes(label=z), nudge_x = -10, ylim = c(-5, 5),
                   segment.alpha = .5, box.padding = 0.8, xlim = c(-5, 35)) +
  scale_x_continuous(limits = c(-5, 35)) +
  scale_y_continuous(limits = c(-5, 5)) +
  theme(legend.position = "none")

image

By playing aroudn with the size, limits and padding, you can get a more "extreme" or "edgy" result.

marcopm94 avatar Apr 09 '20 17:04 marcopm94

Thanks for your help, Michał! 😀

I hadn't realized that nudge_x and nudge_y could be used in such a way.

After reading your note, maybe we should consider allowing a vector for direction... will need to think about that.

Others have also mentioned that something more expressive for direction instead of "x", "y", "both" could be useful.

slowkow avatar Apr 09 '20 23:04 slowkow