Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make stacked circle plot without coord_polar

I've got a dataset similar to this:

x <- 100 - abs(rnorm(1e6, 0, 5))
y <- 50 + rnorm(1e6, 0, 3)
dist <- sqrt((x - 100)^2 + (y - 50)^2)
z <- exp(-(dist / 8)^2)

which can be visualised as follows:

data.frame(x, y, z) %>%  
  ggplot() + geom_point(aes(x, y, color = z))

enter image description here

What I would like to do is a stacked half-circle plot with averaged value of z in subsequent layers. I think it can be done with the combination of geom_col and coord_polar(), although the farthest I can get is

data.frame(x, y, z, dist) %>% 
  mutate(dist_fct = cut(dist, seq(0, max(dist), by = 5))) %>% 
  ggplot() + geom_bar(aes(x = 1, y = 1, fill = dist_fct), stat = 'identity', position = 'fill') +
  coord_polar()

enter image description here

which is obviously far from the expectation (layers should be of equal size, plot should be clipped on the right half).

The problem is that I can't really use coord_polar() due to further use of annotate_custom(). So my question are:

  • can plot like this can be done without coord_polar()?
  • If not, how can it be done with coord_polar()?

The result should be similar to a graphic below, except from plotting layers constructed from points I would like to plot only layers as a whole with color defined as an average value of z inside a layer. enter image description here

like image 245
Kuba_ Avatar asked Oct 29 '18 07:10

Kuba_


2 Answers

If you want simple radius bands, perhaps something like this would work as you pictured it in your question:

# your original sample data
x <- 100 - abs(rnorm(1e6, 0, 5))
y <- 50 + rnorm(1e6, 0, 3)
dist <- sqrt((x - 100)^2 + (y - 50)^2)

nbr_bands <- 6  # set nbr of bands to plot 

# calculate width of bands
band_width <- max(dist)/(nbr_bands-1)

# dist div band_width yields an integer 0 to nbr bands
# as.factor makes it categorical, which is what you want for the plot
band = as.factor(dist %/% (band_width))

library(dplyr)
library(ggplot2)
data.frame(x, y, band) %>%  
  ggplot() + geom_point(aes(x, y, color = band)) + coord_fixed() +
  theme_dark()  # dark theme

enter image description here

Edit to elaborate:

As you first attempted, it would be nice to use the very handy cut() function to calculate the radius color categories.

One way to get categorical (discrete) colors, rather than continuous shading, for your plot color groups is to set your aes color= to a factor column.

To directly get a factor from cut() you may use option ordered_result=TRUE:

band <- cut(dist, nbr_bands, ordered_result=TRUE, labels=1:nbr_bands)  # also use `labels=` to specify your own labels

data.frame(x, y, band) %>%
  ggplot() + geom_point(aes(x, y, color = band)) + coord_fixed() 

enter image description here

Or more simply you may use cut() without options and convert to a factor using as.factor():

band <- as.factor( cut(dist, nbr_bands, labels=FALSE) )

data.frame(x, y, band) %>%
  ggplot() + geom_point(aes(x, y, color = band)) + coord_fixed() 

enter image description here

like image 65
krads Avatar answered Oct 05 '22 23:10

krads


Sounds like you may find the circle & arc plotting functions from the ggforce package useful:

# data
set.seed(1234)
df <- data.frame(x = 100 - abs(rnorm(1e6, 0, 5)),
                 y = 50 + rnorm(1e6, 0, 3)) %>%  
  mutate(dist = sqrt((x - 100)^2 + (y - 50)^2)) %>%
  mutate(z = exp(-(dist / 8)^2))

# define cut-off values
cutoff.values <- seq(0, ceiling(max(df$dist)), by = 5)

df %>%
  # calculate the mean z for each distance band
  mutate(dist_fct = cut(dist, cutoff.values)) %>%
  group_by(dist_fct) %>%
  summarise(z = mean(z)) %>%
  ungroup() %>%

  # add the cutoff values to the dataframe for inner & outer radius
  arrange(dist_fct) %>%
  mutate(r0 = cutoff.values[-length(cutoff.values)],
         r = cutoff.values[-1]) %>%

  # add coordinates for circle centre
  mutate(x = 100, y = 50) %>%

  # plot
  ggplot(aes(x0 = x, y0 = y, 
             r0 = r0, r = r, 
             fill = z)) +
  geom_arc_bar(aes(start = 0, end = 2 * pi), 
               color = NA) + # hide outline

  # force equal aspect ratio in order to get true circle
  coord_equal(xlim = c(70, 100), expand = FALSE)

Plot generation took <1s on my machine. Yours may differ.

plot

like image 42
Z.Lin Avatar answered Oct 05 '22 23:10

Z.Lin