Techno Blender
Digitally Yours.

How to Make a Spider Chart in R Using ggplot2 | by Zvonimir Boban | Jan, 2023

0 50


Photo by Divyadarshi Acharya on Unsplash

Knowing that a radar chart can make for a compelling visualization, I tried finding a suitable R library. I was surprised when my search yielded practically no results. The only package I found was ggradar which is not on CRAN, but can only be installed from GitHub.

I decided to give it a try using the data on the Titanic crash from the titanic package. In order to calculate the data summaries I wanted and get the data to the shape needed for plotting, I used the dplyr , tidyr, tibble, purr and scales packages. If you are unfamiliar with these, check out my earlier article on data wrangling with R.

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)

Titanic <- titanic_train

Titanic_gr1 <-
Titanic %>%
select(Survived:Fare) %>%
group_by(Pclass) %>%
summarise(across(c(Age, Fare), mean, na.rm = TRUE))

Titanic_gr2 <-
Titanic %>%
select(Survived:Fare) %>%
group_by(Pclass, Survived) %>%
summarise(N = n()) %>%
pivot_wider(names_from = Survived, values_from = N) %>%
mutate("Survived (%)" = `1`/(`0` + `1`)) %>%
select(1,4)

Titanic_gr3 <-
Titanic %>%
select(Survived:Fare) %>%
group_by(Pclass, Sex) %>%
summarise(N = n()) %>%
pivot_wider(names_from = Sex, values_from = N)

Titanic_gr <- reduce(list(Titanic_gr1,
Titanic_gr2,
Titanic_gr3), left_join) %>%
rename(Male = "male", Female = "female")

Titanic_gr

# A tibble: 3 × 6
Pclass Age Fare `Survived (%)` Female Male
<int> <dbl> <dbl> <dbl> <int> <int>
1 1 38.2 84.2 0.630 94 122
2 2 29.9 20.7 0.473 76 108
3 3 25.1 13.7 0.242 144 347

After installing and loading the ggradarpackage and rescaling the data to a (0, 1) interval, we can create the radar chart by invoking the ggradar function.

library(ggplot2)
library(scales)
library(devtools)
install_github("ricardo-bion/ggradar", dependencies=TRUE)
library(ggradar)

Titanic_gr %>%
mutate(across(-Pclass, normiranje)) %>%
ggradar(legend.title = "Passenger class") +
theme(legend.position = "bottom", legend.title = element_text(size = 17))

A radar chart made using the ggradar package. Image by Author

Even though the obtained chart was neat and visually appealing, I wanted to have separate axis for each of the variables. Although radar and spider chart names are used as synonyms, I think a spider chart is more appropriate for the latter situation in which every variable has its own axis, and radar chart for the former like in the above image. Unfortunately, as I was more interested in creating a spider chart, and no such option was available using this package, I was left to my own devices.

The beauty of ggplot2 is the underlying grammar of graphics, allowing for creation of graphs by stacking multiple layers on top of one another. This powerful concept lets us create essentially any visualization, as long as we know how to code it. I’ll be using it to construct a spider chart from scratch.

The philosophy behind chart construction with the ggplot2 package. Image by Author

The layered approach calls for separate construction of different aspects of the graph. First we will create the chart outline. Since spider charts deal with polar coordinates, I made a function which calculates the coordinates of the polygon tips depending on the number of variables in the dataset.

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(scales)
library(ggplot2)

p_data <- Titanic_gr

circle_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r)
}

central_distance <- 0.2

step_1 <- map_df(seq(0, 1, 0.25) + central_distance, circle_coords) %>%
ggplot(aes(x, y)) +
geom_polygon(data = circle_coords(1 + central_distance),
alpha = 1, fill = "gray97") +
geom_path(aes(group = r), lty = 2, alpha = 0.5) +
theme_void()

Step 1: Creating the background for the chart. Image by Author

Next, we have to calculate the coordinates for the axes and add them to the chart.

axis_coords <- function(n_axis){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2
x1 <- central_distance*cos(fi)
y1 <- central_distance*sin(fi)
x2 <- (1 + central_distance)*cos(fi)
y2 <- (1 + central_distance)*sin(fi)

tibble(x = c(x1, x2), y = c(y1, y2), id = rep(1:n_axis, 2))
}

step_2 <- step_1 + geom_line(data = axis_coords(ncol(p_data) - 1),
aes(x, y, group = id), alpha = 0.3)

Addition of axes for each of the variables in the dataset. Image by Author

Now we can add the textual labels and names of the axes.

text_data <- mtcars_real %>%
select(-group) %>%
map_df(~ min(.) + (max(.) - min(.)) * seq(0, 1, 0.25)) %>%
mutate(r = seq(0, 1, 0.25)) %>%
pivot_longer(-r, names_to = "parameter", values_to = "value")

text_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2 + 0.01*2*pi/r
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r = r - central_distance)
}

labels_data <- map_df(seq(0, 1, 0.25) + central_distance, text_coords) %>%
bind_cols(text_data %>% select(-r))

step_3 <- step_2 +
geom_text(data = labels_data, aes(x, y, label = value), alpha = 0.65) +
geom_text(data = text_coords(1 + central_distance + 0.2),
aes(x, y), label = labels_data$parameter[1:(ncol(p_data)-1)])

Addition of axes names and labels. Image by Author

The only thing left to do is overlaying of the data points.

rescaled_coords <- function(r, n_axis){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
tibble(r, fi) %>% mutate(x = r*cos(fi), y = r*sin(fi)) %>% select(-fi)
}

rescaled_data <- p_data %>%
mutate(across(-group, rescale)) %>%
mutate(copy = pull(., 2)) %>%
pivot_longer(-group, names_to = "parameter", values_to = "value") %>%
group_by(group) %>%
mutate(coords = rescaled_coords(value + central_distance, ncol(p_data) - 1)) %>%
unnest

step_4 <- step_3 +
geom_point(data = rescaled_data,
aes(x, y, group = group, col = group),
size = 3) +
geom_path(data = rescaled_data,
aes(x, y, group = group, col = group),
size = 1)

Overlaying the chart with the data points. Image by Author

And some final aesthetic touches…

step_5 <- step_4 + 
labs(col = "Car name") +
theme(legend.position = "bottom",
legend.text = element_text(size = 12),
legend.title = element_text(size = 12))
The final spider chart. Image by Author

Ta-daa, our work here is done. Let’s just take a moment more to comment on the numbers displayed. The 1st class passengers were the oldest and the wealthiest of the three. The 3rd class passengers had the highest number of both male and female passengers and were the youngest group — probably mostly young people and families in search for better life abroad. However, the 1st class passengers had the highest survival rate, and the 3rd the lowest. This is probably partly due to the 1st class quarters being closer to the boat deck and partly due to the higher proportion of women in that class (since woman and children were rescued first).

To make the plots easier to reproduce, I made a function from the above code.

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(scales)
library(ggplot2)

ggspider <- function(p_data,
legend_title = "Group",
background_color = "gray97",
area_fill = TRUE,
central_distance = 0.2,
axis_name_offset = 0.2){

circle_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r)
}

(step_1 <- map_df(seq(0, 1, 0.25) + central_distance, circle_coords) %>%
ggplot(aes(x, y)) +
geom_polygon(data = circle_coords(1 + central_distance), alpha = 1, fill = background_color) +
geom_path(aes(group = r), lty = 2, alpha = 0.5) +
theme_void())

axis_coords <- function(n_axis){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2
x1 <- central_distance*cos(fi)
y1 <- central_distance*sin(fi)
x2 <- (1 + central_distance)*cos(fi)
y2 <- (1 + central_distance)*sin(fi)

tibble(x = c(x1, x2), y = c(y1, y2), id = rep(1:n_axis, 2))
}

text_data <- p_data %>%
select(-group) %>%
map_df(~ min(.) + (max(.) - min(.)) * seq(0, 1, 0.25)) %>%
mutate(r = seq(0, 1, 0.25)) %>%
pivot_longer(-r, names_to = "parameter", values_to = "value")

text_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2 + 0.01*2*pi/r
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r = r - central_distance)
}

labels_data <- map_df(seq(0, 1, 0.25) + central_distance, text_coords) %>%
bind_cols(text_data %>% select(-r))

rescaled_coords <- function(r, n_axis){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
tibble(r, fi) %>% mutate(x = r*cos(fi), y = r*sin(fi)) %>% select(-fi)
}

rescaled_data <- p_data %>%
mutate(across(-group, rescale)) %>%
mutate(copy = pull(., 2)) %>%
pivot_longer(-group, names_to = "parameter", values_to = "value") %>%
group_by(group) %>%
mutate(coords = rescaled_coords(value + central_distance, ncol(p_data) - 1)) %>%
unnest

step_1 +
geom_line(data = axis_coords(ncol(p_data) - 1),
aes(x, y, group = id), alpha = 0.3) +
geom_point(data = rescaled_data,
aes(x, y, group = group, col = group), size = 3) +
geom_path(data = rescaled_data,
aes(x, y, group = group, col = group), size = 1) +
{if(area_fill == TRUE) geom_polygon(data = rescaled_data,
aes(x, y, group = group,
col = group, fill = group),
size = 1, alpha = 0.05, show.legend = FALSE)} +
geom_text(data = labels_data,
aes(x, y, label = value), alpha = 0.65) +
geom_text(data = text_coords(1 + central_distance + axis_name_offset),
aes(x, y), label = labels_data$parameter[1:(ncol(p_data)-1)]) +
labs(col = legend_title) +
theme(legend.position = "bottom",
legend.text = element_text(size = 12),
legend.title = element_text(size = 12))
}

Now, we can easily test the function on some different data, such as the mtcars dataset built into R.

p_data <- mtcars %>%
rownames_to_column(var = "group") %>%
as_tibble() %>%
tail(3) %>%
select(1:7)

ggspider(p_data)

Spider chart of the mtcars dataset. Image by Author

This article shows how to construct a spider chart from scratch in R. Although no R package I know of currently supports this kind of chart, utilizing the layered grammar of graphics approach from the ggplot2 package provided me with the tools to create one myself. Of course, there is still room for progress, especially with regard to the aesthetical aspect of the chart, but that is a topic for some other article.

That’s all folks. I hope you found the article useful and will use it to make many more neat spider charts in the future. Enjoy!


Photo by Divyadarshi Acharya on Unsplash

Knowing that a radar chart can make for a compelling visualization, I tried finding a suitable R library. I was surprised when my search yielded practically no results. The only package I found was ggradar which is not on CRAN, but can only be installed from GitHub.

I decided to give it a try using the data on the Titanic crash from the titanic package. In order to calculate the data summaries I wanted and get the data to the shape needed for plotting, I used the dplyr , tidyr, tibble, purr and scales packages. If you are unfamiliar with these, check out my earlier article on data wrangling with R.

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)

Titanic <- titanic_train

Titanic_gr1 <-
Titanic %>%
select(Survived:Fare) %>%
group_by(Pclass) %>%
summarise(across(c(Age, Fare), mean, na.rm = TRUE))

Titanic_gr2 <-
Titanic %>%
select(Survived:Fare) %>%
group_by(Pclass, Survived) %>%
summarise(N = n()) %>%
pivot_wider(names_from = Survived, values_from = N) %>%
mutate("Survived (%)" = `1`/(`0` + `1`)) %>%
select(1,4)

Titanic_gr3 <-
Titanic %>%
select(Survived:Fare) %>%
group_by(Pclass, Sex) %>%
summarise(N = n()) %>%
pivot_wider(names_from = Sex, values_from = N)

Titanic_gr <- reduce(list(Titanic_gr1,
Titanic_gr2,
Titanic_gr3), left_join) %>%
rename(Male = "male", Female = "female")

Titanic_gr

# A tibble: 3 × 6
Pclass Age Fare `Survived (%)` Female Male
<int> <dbl> <dbl> <dbl> <int> <int>
1 1 38.2 84.2 0.630 94 122
2 2 29.9 20.7 0.473 76 108
3 3 25.1 13.7 0.242 144 347

After installing and loading the ggradarpackage and rescaling the data to a (0, 1) interval, we can create the radar chart by invoking the ggradar function.

library(ggplot2)
library(scales)
library(devtools)
install_github("ricardo-bion/ggradar", dependencies=TRUE)
library(ggradar)

Titanic_gr %>%
mutate(across(-Pclass, normiranje)) %>%
ggradar(legend.title = "Passenger class") +
theme(legend.position = "bottom", legend.title = element_text(size = 17))

A radar chart made using the ggradar package. Image by Author

Even though the obtained chart was neat and visually appealing, I wanted to have separate axis for each of the variables. Although radar and spider chart names are used as synonyms, I think a spider chart is more appropriate for the latter situation in which every variable has its own axis, and radar chart for the former like in the above image. Unfortunately, as I was more interested in creating a spider chart, and no such option was available using this package, I was left to my own devices.

The beauty of ggplot2 is the underlying grammar of graphics, allowing for creation of graphs by stacking multiple layers on top of one another. This powerful concept lets us create essentially any visualization, as long as we know how to code it. I’ll be using it to construct a spider chart from scratch.

The philosophy behind chart construction with the ggplot2 package. Image by Author

The layered approach calls for separate construction of different aspects of the graph. First we will create the chart outline. Since spider charts deal with polar coordinates, I made a function which calculates the coordinates of the polygon tips depending on the number of variables in the dataset.

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(scales)
library(ggplot2)

p_data <- Titanic_gr

circle_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r)
}

central_distance <- 0.2

step_1 <- map_df(seq(0, 1, 0.25) + central_distance, circle_coords) %>%
ggplot(aes(x, y)) +
geom_polygon(data = circle_coords(1 + central_distance),
alpha = 1, fill = "gray97") +
geom_path(aes(group = r), lty = 2, alpha = 0.5) +
theme_void()

Step 1: Creating the background for the chart. Image by Author

Next, we have to calculate the coordinates for the axes and add them to the chart.

axis_coords <- function(n_axis){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2
x1 <- central_distance*cos(fi)
y1 <- central_distance*sin(fi)
x2 <- (1 + central_distance)*cos(fi)
y2 <- (1 + central_distance)*sin(fi)

tibble(x = c(x1, x2), y = c(y1, y2), id = rep(1:n_axis, 2))
}

step_2 <- step_1 + geom_line(data = axis_coords(ncol(p_data) - 1),
aes(x, y, group = id), alpha = 0.3)

Addition of axes for each of the variables in the dataset. Image by Author

Now we can add the textual labels and names of the axes.

text_data <- mtcars_real %>%
select(-group) %>%
map_df(~ min(.) + (max(.) - min(.)) * seq(0, 1, 0.25)) %>%
mutate(r = seq(0, 1, 0.25)) %>%
pivot_longer(-r, names_to = "parameter", values_to = "value")

text_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2 + 0.01*2*pi/r
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r = r - central_distance)
}

labels_data <- map_df(seq(0, 1, 0.25) + central_distance, text_coords) %>%
bind_cols(text_data %>% select(-r))

step_3 <- step_2 +
geom_text(data = labels_data, aes(x, y, label = value), alpha = 0.65) +
geom_text(data = text_coords(1 + central_distance + 0.2),
aes(x, y), label = labels_data$parameter[1:(ncol(p_data)-1)])

Addition of axes names and labels. Image by Author

The only thing left to do is overlaying of the data points.

rescaled_coords <- function(r, n_axis){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
tibble(r, fi) %>% mutate(x = r*cos(fi), y = r*sin(fi)) %>% select(-fi)
}

rescaled_data <- p_data %>%
mutate(across(-group, rescale)) %>%
mutate(copy = pull(., 2)) %>%
pivot_longer(-group, names_to = "parameter", values_to = "value") %>%
group_by(group) %>%
mutate(coords = rescaled_coords(value + central_distance, ncol(p_data) - 1)) %>%
unnest

step_4 <- step_3 +
geom_point(data = rescaled_data,
aes(x, y, group = group, col = group),
size = 3) +
geom_path(data = rescaled_data,
aes(x, y, group = group, col = group),
size = 1)

Overlaying the chart with the data points. Image by Author

And some final aesthetic touches…

step_5 <- step_4 + 
labs(col = "Car name") +
theme(legend.position = "bottom",
legend.text = element_text(size = 12),
legend.title = element_text(size = 12))
The final spider chart. Image by Author

Ta-daa, our work here is done. Let’s just take a moment more to comment on the numbers displayed. The 1st class passengers were the oldest and the wealthiest of the three. The 3rd class passengers had the highest number of both male and female passengers and were the youngest group — probably mostly young people and families in search for better life abroad. However, the 1st class passengers had the highest survival rate, and the 3rd the lowest. This is probably partly due to the 1st class quarters being closer to the boat deck and partly due to the higher proportion of women in that class (since woman and children were rescued first).

To make the plots easier to reproduce, I made a function from the above code.

library(dplyr)
library(tidyr)
library(tibble)
library(purrr)
library(scales)
library(ggplot2)

ggspider <- function(p_data,
legend_title = "Group",
background_color = "gray97",
area_fill = TRUE,
central_distance = 0.2,
axis_name_offset = 0.2){

circle_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r)
}

(step_1 <- map_df(seq(0, 1, 0.25) + central_distance, circle_coords) %>%
ggplot(aes(x, y)) +
geom_polygon(data = circle_coords(1 + central_distance), alpha = 1, fill = background_color) +
geom_path(aes(group = r), lty = 2, alpha = 0.5) +
theme_void())

axis_coords <- function(n_axis){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2
x1 <- central_distance*cos(fi)
y1 <- central_distance*sin(fi)
x2 <- (1 + central_distance)*cos(fi)
y2 <- (1 + central_distance)*sin(fi)

tibble(x = c(x1, x2), y = c(y1, y2), id = rep(1:n_axis, 2))
}

text_data <- p_data %>%
select(-group) %>%
map_df(~ min(.) + (max(.) - min(.)) * seq(0, 1, 0.25)) %>%
mutate(r = seq(0, 1, 0.25)) %>%
pivot_longer(-r, names_to = "parameter", values_to = "value")

text_coords <- function(r, n_axis = ncol(p_data) - 1){
fi <- seq(0, (1 - 1/n_axis)*2*pi, (1/n_axis)*2*pi) + pi/2 + 0.01*2*pi/r
x <- r*cos(fi)
y <- r*sin(fi)

tibble(x, y, r = r - central_distance)
}

labels_data <- map_df(seq(0, 1, 0.25) + central_distance, text_coords) %>%
bind_cols(text_data %>% select(-r))

rescaled_coords <- function(r, n_axis){
fi <- seq(0, 2*pi, (1/n_axis)*2*pi) + pi/2
tibble(r, fi) %>% mutate(x = r*cos(fi), y = r*sin(fi)) %>% select(-fi)
}

rescaled_data <- p_data %>%
mutate(across(-group, rescale)) %>%
mutate(copy = pull(., 2)) %>%
pivot_longer(-group, names_to = "parameter", values_to = "value") %>%
group_by(group) %>%
mutate(coords = rescaled_coords(value + central_distance, ncol(p_data) - 1)) %>%
unnest

step_1 +
geom_line(data = axis_coords(ncol(p_data) - 1),
aes(x, y, group = id), alpha = 0.3) +
geom_point(data = rescaled_data,
aes(x, y, group = group, col = group), size = 3) +
geom_path(data = rescaled_data,
aes(x, y, group = group, col = group), size = 1) +
{if(area_fill == TRUE) geom_polygon(data = rescaled_data,
aes(x, y, group = group,
col = group, fill = group),
size = 1, alpha = 0.05, show.legend = FALSE)} +
geom_text(data = labels_data,
aes(x, y, label = value), alpha = 0.65) +
geom_text(data = text_coords(1 + central_distance + axis_name_offset),
aes(x, y), label = labels_data$parameter[1:(ncol(p_data)-1)]) +
labs(col = legend_title) +
theme(legend.position = "bottom",
legend.text = element_text(size = 12),
legend.title = element_text(size = 12))
}

Now, we can easily test the function on some different data, such as the mtcars dataset built into R.

p_data <- mtcars %>%
rownames_to_column(var = "group") %>%
as_tibble() %>%
tail(3) %>%
select(1:7)

ggspider(p_data)

Spider chart of the mtcars dataset. Image by Author

This article shows how to construct a spider chart from scratch in R. Although no R package I know of currently supports this kind of chart, utilizing the layered grammar of graphics approach from the ggplot2 package provided me with the tools to create one myself. Of course, there is still room for progress, especially with regard to the aesthetical aspect of the chart, but that is a topic for some other article.

That’s all folks. I hope you found the article useful and will use it to make many more neat spider charts in the future. Enjoy!

FOLLOW US ON GOOGLE NEWS

Read original article here

Denial of responsibility! Techno Blender is an automatic aggregator of the all world’s media. In each content, the hyperlink to the primary source is specified. All trademarks belong to their rightful owners, all materials to their authors. If you are the owner of the content and do not want us to publish your materials, please contact us by email – [email protected]. The content will be deleted within 24 hours.

Leave a comment