::p_load(plotly, ggtern, tidyverse) pacman
The downloaded binary packages are in
/var/folders/nr/x4l8hvc562g81px_9hrd30_r0000gn/T//RtmpWg1pAz/downloaded_packages
June 20, 2025
Ternary plots are a way of displaying the distribution and variability of three-part compositional data. (For example, the proportion of aged, economy active and young population or sand, silt, and clay in soil.)
The display is a triangle with sides scaled from 0 to 1. Each side represents one of the three components. A point is plotted so that a line drawn perpendicular from the point to each leg of the triangle intersect at the component values of the point.
In this hands-on, I will build ternary plot programmatically using R for visualising and analysing population structure of Singapore. Here are the 4 steps:
mutate()
function of dplyr package.ggtern()
function of ggtern package.plot-ly()
function of Plotly R package.2 main R packages will be used.
R Package | Overview |
---|---|
ggtern | a ggplot extension that plots ternary diagrams. The package will be used to plot static ternary plots. |
Plotply R | an R package for creating interactive web-based graphs via plotly’s JavaScript graphing library, plotly.js. The plotly R library contains the ggplotly function, which will convert ggplot2 figures into a Plotly object. |
tidyverse | selected tidyverse family packages: readr, dplyr and tidyr are installed and loaded. |
Version 3.2.1 of ggplot2 will be installed instead of the latest version of ggplot2, because the current version of ggtern package is not compatible to the latest version of ggplot2.
The Singapore Residents by Planning AreaSubzone, Age Group, Sex and Type of Dwelling, June 2000-2018 data will be used.
File name: respopagsex2000to2018_tidy.csv
# A tibble: 6 × 5
PA SZ AG Year Population
<chr> <chr> <chr> <dbl> <dbl>
1 Ang Mo Kio Ang Mo Kio Town Centre AGE0-4 2011 290
2 Ang Mo Kio Ang Mo Kio Town Centre AGE0-4 2012 270
3 Ang Mo Kio Ang Mo Kio Town Centre AGE0-4 2013 260
4 Ang Mo Kio Ang Mo Kio Town Centre AGE0-4 2014 250
5 Ang Mo Kio Ang Mo Kio Town Centre AGE0-4 2015 260
6 Ang Mo Kio Ang Mo Kio Town Centre AGE0-4 2016 250
Use mutate()
function of dplyr package to derive 3 new measures, namely: young, active and old.
#Deriving the young, economy active and old measures
agpop_mutated <- pop_data %>%
mutate(`Year` = as.character(Year)) %>%
spread(AG, Population) %>% #turn the values in Population col into AG cols.
mutate(YOUNG = rowSums(.[4:8])) %>% #Age 0-24
mutate(ACTIVE = rowSums(.[9:16])) %>% #Age 25-64
mutate(OLD = rowSums(.[17:21])) %>% #Age >65
mutate(TOTAL = rowSums(.[22:24])) %>% #Age
filter(Year == 2018) %>%
filter(TOTAL >0)
Because mutating expressions are computed within groups, they may yield different results on grouped tibbles. This will be the case as soon as an aggregating, lagging, or ranking function is involved. Compare this ungrouped mutate:
starwars %>%
select(name, mass, species) %>%
mutate(mass_norm = mass / mean(mass, na.rm = TRUE))
starwars %>%
select(name, mass, species) %>%
group_by(species) %>%
mutate(mass_norm = mass / mean(mass, na.rm = TRUE))
The former normalises mass
by the global average whereas the latter normalises by the averages within species levels.
Use ggtern() function of ggtern package to create a simple ternary plot:
Use plot_ly()
to create an interactive plot.:
# Function for creating annotation object too
label <- function(txt) {
list(
text = txt,
x = 0.1, y = 1, #Position of the annotation in the plot
ax = 0, ay = 0, #annotation has no arrow
xref = "paper", yref = "paper", #Positioning is relative to the entire figure, not data points
align = "center",
font = list(family = "Calibri", size = 15, color = "white"),
bgcolor = "#000000", # Background color of the annotation box (light gray)
bordercolor = "black",
borderwidth = 2
)
}
# Function for creating axis formatting too
axis <- function(txt) {
list(
title = txt, tickformat = ".0%", tickfont = list(size = 10)
)
}
ternaryAxes <- list(
aaxis = axis("Young"),
baxis = axis("Active"),
caxis = axis("Old")
)
# Initiate a plotly visualization
plot_ly(
agpop_mutated,
a = ~YOUNG,
b = ~ACTIVE,
c = ~OLD,
color = I("black"),
type = "scatterternary"
) %>%
layout(
annotations = label("Ternary Markers"),
ternary = ternaryAxes
)