Making `coord_polar` behave like standard polar coordinate plotting?

ggplot2

#1

This question is motivated by this post from @cawthm.

The goal was to plot a function in polar coordinates. Several attempts are shown below (note: reprex mostly by @cawthm, posted here for convenience). :

library(tidyverse)
#> Loading tidyverse: ggplot2
#> Loading tidyverse: tibble
#> Loading tidyverse: tidyr
#> Loading tidyverse: readr
#> Loading tidyverse: purrr
#> Loading tidyverse: dplyr
#> Conflicts with tidy packages ----------------------------------------------
#> filter(): dplyr, stats
#> lag():    dplyr, stats

# first, generate some data: 2*pi radians in a circle; we have three
# circles' worth of rads
x <- tibble(x = head(seq(0, 3 * (2 * pi), by = pi/180), -1))

# compute our sine wave; also, we may need to compute rads, because we'll
# want our plot theta to be restricted from 0 to 2pi, and as far as the axis
# is concerned, 2pi = 4pi = 6pi = etc
x <- x %>% mutate(sine = sin(3 * x)/3 + 1 + x/180, rads = x%%(2 * pi))

# Atempt 1
ggplot(x) + geom_line(aes(x = x, y = sine)) + ylim(c(0, 1.5)) + coord_polar(theta = "x")


## THE PROBLEM: the above spreads the theta over the entire x, ie, it ranges
## up to 3 * (2 * pi), instead of the standard 2*pi

# Attempt 2. Let's change the x aes to our rads variable
ggplot(x) + geom_line(aes(x = rads, y = sine)) + ylim(c(0, 1.5)) + coord_polar(theta = "x")



# Attempt 3. since the values of the sine are in order, maybe I can use
# geom_path...
ggplot(x) + geom_path(aes(x = rads, y = sine)) + ylim(c(0, 1.5)) + coord_polar(theta = "x")


# The fix involves changing to back to cartesian coordinates.

x <- tibble(x = head(seq(0, 3 * (2 * pi), by = pi/180), -1))

x <- x %>% mutate(sine = sin(3 * x)/3 + 1 + x/180, rads = x%%(2 * pi))

x <- x %>% mutate(x = sine * cos(rads), y = sine * sin(rads))
ggplot(x) + geom_path(aes(x = x, y = y))

The three attempts have issues because coord_polar naturally extends the ‘x-axis’ (the angle) to extend the entire range of x. My understanding is it does this for purposes of making pie charts - which makes sense. My fix (last example) was to just convert everything back to cartesian coordinates and avoid using cood_polar altogether. However, is there a way to use coord_polar to plot in standard polar coordinates? That is, all angles are modulo 2*pi and line segments are connected in the order of the angles (before the modulo operation).


#2

Might I suggest you add a reprex here with both the original/problematic version and how you fixed it (yes, I know the latter is already in the other thread, but it’s nice to look at things all in one place)? The reprex package is great for visualization stuff, because it includes the code and visualizations in the output at the click of a button (all you have to do is paste!) If you’re not familiar with reprexes, I really like the Magic Reprex post by Nick Tierney.

Couple of things possibly worth looking at:

  • ggplot2-extensions (has a nice gallery, and I’m not sure if any of them do what you’re describing, but this could be something to build out as an extension, if not)
  • Circular Visualization in R great bookdown book by Zuguang Gu, uses a package called circos, which does some really advanced stuff with circular visualization

#3

thanks @mara. Sometimes I need a reminder to follow the same advice I give :). Question has been edited to include a reprex.


#4

what’s wrong with using a group variable?

library(tidyverse)

x <- tibble(x = seq(0, 3 * (2 * pi), length.out = 1e4)) 
x <- x %>% mutate(sine = sin(3*x)/3 + 1 + x/180, 
                  rads = x %% (2*pi), 
                  id=cumsum(c(0,diff(x %/% (2*pi)))))

ggplot(x) + 
  coord_polar(theta = 'x')+ 
  geom_line(aes(x = rads, y = sine, group=factor(id))) + 
  ylim(c(0,1.5)) 

#5

Just adding a reprex of @baptiste’s code for the visual! :+1:

library(tidyverse)

x <- tibble(x = seq(0, 3 * (2 * pi), length.out = 1e4)) 
x <- x %>% mutate(sine = sin(3*x)/3 + 1 + x/180, 
                  rads = x %% (2*pi), 
                  id=cumsum(c(0,diff(x %/% (2*pi)))))

ggplot(x) + 
  coord_polar(theta = 'x')+ 
  geom_line(aes(x = rads, y = sine, group=factor(id))) + 
  ylim(c(0,1.5)) 

Created on 2017-12-22 by the reprex package (v0.1.1.9000).


#6

thanks, @baptiste and @mara.

Grouping was actually tried by @cawthm in the original post, but I left that attempt out of the reprex (apologies). The small issue with it is it is sensitive to the length.out parameter. Even at 1e4 in your example, you can see a small gap (if you look hard) at theta = 0 when the groups change. You can see this more with length.out = 500.

library(tidyverse)
#> Loading tidyverse: ggplot2
#> Loading tidyverse: tibble
#> Loading tidyverse: tidyr
#> Loading tidyverse: readr
#> Loading tidyverse: purrr
#> Loading tidyverse: dplyr
#> Conflicts with tidy packages ----------------------------------------------
#> filter(): dplyr, stats
#> lag():    dplyr, stats

x <- tibble(x = seq(0, 3 * (2 * pi), length.out = 500))
x <- x %>% mutate(sine = sin(3 * x)/3 + 1 + x/180, rads = x%%(2 * pi), id = cumsum(c(0, 
  diff(x%/%(2 * pi)))))

ggplot(x) + coord_polar(theta = "x") + geom_line(aes(x = rads, y = sine, group = factor(id))) + 
  ylim(c(0, 1.5))

So sure - we can increase this parameter until it looks good, but it’s not really the behavior I’d expect when plotting in polar coordinates…I’d expect a smooth transition and path connections at theta = 0. Does that make sense?

Trying to find some time to explore the links @mara provided. I’d guess there is a good chance there is something there.


#7

The munching process is not particularly well documented or transparent; from what I understand it tries to keep a minimal segment length in the transformed space so that curves appear smooth. However the discontinuity at (2pi) probably messes with this objective at the end/start of different curves.
I guess the point is that geom_path with no grouping variable should arguably work, and the circular artefacts could be reported as an issue. As a workaround, grouping and increased resolution can help to some extent.


#8

I agree that is difficult to decipher the documentation (or even the source code), but I’m not convinced it’s an issue - see my explanation/hypothesis for why the circular artifacts occur. I have yet to hear feedback from that explanation, but the behavior seems consistent to me. Also, the function is not discontinuous at 2*pi, this was induced by the grouping.

My guess is that the behavior has to do with what I understand to be the main reason for ggplot2::coord_polar - to make pie charts. I’m hoping someone here can provide some insight into that.


#9

This a trick I’ve found somewhere: it defines a new coordinate system in which the lines are straight.

coord_radar <- function (theta = "x", start = 0, direction = 1) 
{
 theta <- match.arg(theta, c("x", "y"))
 r <- if (theta == "x") 
        "y"
      else "x"
 ggproto("CoordRadar", CoordPolar, theta = theta, r = r, start = start, 
      direction = sign(direction),
      is_linear = function(coord) TRUE)
}

More details on http://www.cmap.polytechnique.fr/~lepennec/R/Radar/RadarAndParallelPlots.html